mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-22 20:13:58 +08:00
begin adding support for in-document security handlers to support aes 128/256 encryption #34
This commit is contained in:
67
src/UglyToad.PdfPig/Encryption/CryptDictionary.cs
Normal file
67
src/UglyToad.PdfPig/Encryption/CryptDictionary.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
internal class CryptDictionary
|
||||
{
|
||||
public static CryptDictionary Identity { get; } = new CryptDictionary();
|
||||
|
||||
public Method Name { get; }
|
||||
|
||||
public TriggerEvent Event { get; }
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public bool IsIdentity { get; }
|
||||
|
||||
public CryptDictionary(Method name, TriggerEvent @event, int length)
|
||||
{
|
||||
Name = name;
|
||||
Event = @event;
|
||||
Length = length;
|
||||
IsIdentity = false;
|
||||
}
|
||||
|
||||
private CryptDictionary()
|
||||
{
|
||||
Name = Method.None;
|
||||
IsIdentity = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method used by the consumer application to decrypt data.
|
||||
/// </summary>
|
||||
public enum Method
|
||||
{
|
||||
/// <summary>
|
||||
/// The application does not decrypt data but directs the input stream
|
||||
/// to the security handler for decryption.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// The application asks the security handler for the encryption key
|
||||
/// and implicitly decrypts data using the RC4 algorithm.
|
||||
/// </summary>
|
||||
V2,
|
||||
/// <summary>
|
||||
/// (PDF 1.6) The application asks the security handler for the encryption key and implicitly decrypts data using the AES algorithm in Cipher Block Chaining (CBC) mode
|
||||
/// with a 16-byte block size and an initialization vector that is randomly generated and placed as the first 16 bytes in the stream or string.
|
||||
/// </summary>
|
||||
AesV2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The event to be used to trigger the authorization that is required
|
||||
/// to access encryption keys used by this filter.
|
||||
/// </summary>
|
||||
public enum TriggerEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization is required when a document is opened.
|
||||
/// </summary>
|
||||
DocumentOpen,
|
||||
/// <summary>
|
||||
/// Authorization is required when accessing embedded files.
|
||||
/// </summary>
|
||||
EmbeddedFileOpen
|
||||
}
|
||||
}
|
||||
}
|
101
src/UglyToad.PdfPig/Encryption/CryptHandler.cs
Normal file
101
src/UglyToad.PdfPig/Encryption/CryptHandler.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
using System;
|
||||
using Exceptions;
|
||||
using Tokens;
|
||||
|
||||
internal class CryptHandler
|
||||
{
|
||||
private readonly DictionaryToken cryptDictionary;
|
||||
|
||||
public CryptDictionary StreamDictionary { get; }
|
||||
|
||||
public CryptDictionary StringDictionary { get; }
|
||||
|
||||
public CryptHandler(DictionaryToken cryptDictionary,
|
||||
NameToken streamName, NameToken stringName)
|
||||
{
|
||||
if (streamName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(streamName));
|
||||
}
|
||||
|
||||
if (stringName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stringName));
|
||||
}
|
||||
|
||||
this.cryptDictionary = cryptDictionary ?? throw new ArgumentNullException(nameof(cryptDictionary));
|
||||
StreamDictionary = ParseCryptDictionary(cryptDictionary, streamName);
|
||||
StringDictionary = ParseCryptDictionary(cryptDictionary, stringName);
|
||||
}
|
||||
|
||||
public CryptDictionary GetNamedCryptDictionary(NameToken name)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return ParseCryptDictionary(cryptDictionary, name);
|
||||
}
|
||||
|
||||
private static CryptDictionary ParseCryptDictionary(DictionaryToken cryptDictionary, NameToken name)
|
||||
{
|
||||
if (name == NameToken.Identity)
|
||||
{
|
||||
return CryptDictionary.Identity;
|
||||
}
|
||||
|
||||
if (!cryptDictionary.TryGet(name, out DictionaryToken cryptDictionaryToken))
|
||||
{
|
||||
throw new PdfDocumentEncryptedException($"Could not find named crypt filter {name} for decryption in crypt dictionary: {cryptDictionaryToken}.");
|
||||
}
|
||||
|
||||
if (cryptDictionaryToken.TryGet(NameToken.Type, out NameToken typeName) && typeName != NameToken.CryptFilter)
|
||||
{
|
||||
throw new PdfDocumentEncryptedException($"Invalid crypt dictionary type {typeName} for crypt filter {name}: {cryptDictionaryToken}.");
|
||||
}
|
||||
|
||||
var cfmName = cryptDictionaryToken.TryGet(NameToken.Cfm, out NameToken cfm) ? cfm : NameToken.None;
|
||||
|
||||
CryptDictionary.Method method;
|
||||
if (cfmName == NameToken.None)
|
||||
{
|
||||
method = CryptDictionary.Method.None;
|
||||
}
|
||||
else if (cfmName == NameToken.V2)
|
||||
{
|
||||
method = CryptDictionary.Method.V2;
|
||||
}
|
||||
else if (cfmName == NameToken.Aesv2)
|
||||
{
|
||||
method = CryptDictionary.Method.AesV2;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PdfDocumentEncryptedException($"Unrecognized CFM option for crypt filter {cfm}: {cryptDictionaryToken}.");
|
||||
}
|
||||
|
||||
var eventName = cryptDictionaryToken.TryGet(NameToken.AuthEvent, out NameToken auth) ? auth : NameToken.DocOpen;
|
||||
|
||||
CryptDictionary.TriggerEvent @event;
|
||||
if (eventName == NameToken.DocOpen)
|
||||
{
|
||||
@event = CryptDictionary.TriggerEvent.DocumentOpen;
|
||||
}
|
||||
else if (eventName == NameToken.EfOpen)
|
||||
{
|
||||
@event = CryptDictionary.TriggerEvent.EmbeddedFileOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PdfDocumentEncryptedException($"Unrecognized AuthEvent option for crypt filter {eventName}: {cryptDictionaryToken}.");
|
||||
}
|
||||
|
||||
var length = cryptDictionaryToken.TryGet(NameToken.Length, out NumericToken lengthNumeric) ? lengthNumeric.Int : 0;
|
||||
|
||||
return new CryptDictionary(method, @event, length);
|
||||
}
|
||||
}
|
||||
}
|
29
src/UglyToad.PdfPig/Encryption/EncryptionAlgorithmCode.cs
Normal file
29
src/UglyToad.PdfPig/Encryption/EncryptionAlgorithmCode.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
/// <summary>
|
||||
/// A code specifying the algorithm to be used in encrypting and decrypting the document.
|
||||
/// </summary>
|
||||
internal enum EncryptionAlgorithmCode
|
||||
{
|
||||
/// <summary>
|
||||
/// An algorithm that is undocumented and no longer supported.
|
||||
/// </summary>
|
||||
Unrecognized = 0,
|
||||
/// <summary>
|
||||
/// RC4 or AES encryption using a key of 40 bits.
|
||||
/// </summary>
|
||||
Rc4OrAes40BitKey = 1,
|
||||
/// <summary>
|
||||
/// RC4 or AES encryption using a key of more than 40 bits.
|
||||
/// </summary>
|
||||
Rc4OrAesGreaterThan40BitKey = 2,
|
||||
/// <summary>
|
||||
/// An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
|
||||
/// </summary>
|
||||
UnpublishedAlgorithm40To128BitKey = 3,
|
||||
/// <summary>
|
||||
/// The security handler defines the use of encryption and decryption in the document.
|
||||
/// </summary>
|
||||
SecurityHandlerInDocument
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
using System;
|
||||
using Tokenization.Scanner;
|
||||
using Exceptions;
|
||||
using Tokens;
|
||||
using Util;
|
||||
|
||||
@@ -53,129 +53,40 @@
|
||||
OwnerBytes = OtherEncodings.StringAsLatin1Bytes(ownerPasswordCheck);
|
||||
UserBytes = OtherEncodings.StringAsLatin1Bytes(userPasswordCheck);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class EncryptionDictionaryFactory
|
||||
public bool TryGetCryptHandler(out CryptHandler cryptHandler)
|
||||
{
|
||||
public static EncryptionDictionary Read(DictionaryToken encryptionDictionary, IPdfTokenScanner tokenScanner)
|
||||
cryptHandler = null;
|
||||
|
||||
if (EncryptionAlgorithmCode != EncryptionAlgorithmCode.SecurityHandlerInDocument)
|
||||
{
|
||||
if (encryptionDictionary == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Dictionary.TryGet(NameToken.Cf, out DictionaryToken cryptFilterDictionary))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encryptionDictionary));
|
||||
return false;
|
||||
}
|
||||
|
||||
var filter = encryptionDictionary.Get<NameToken>(NameToken.Filter, tokenScanner);
|
||||
var namedFilters = cryptFilterDictionary;
|
||||
|
||||
var code = EncryptionAlgorithmCode.Unrecognized;
|
||||
var streamFilterName = Dictionary.TryGet(NameToken.StmF, out NameToken streamFilterToken) ? streamFilterToken : NameToken.Identity;
|
||||
var stringFilterName = Dictionary.TryGet(NameToken.StrF, out NameToken stringFilterToken) ? stringFilterToken : NameToken.Identity;
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NumericToken vNum))
|
||||
if (streamFilterName != NameToken.Identity && !namedFilters.TryGet(streamFilterName, out _))
|
||||
{
|
||||
code = (EncryptionAlgorithmCode) vNum.Int;
|
||||
throw new PdfDocumentEncryptedException($"Stream filter {streamFilterName} not found in crypt dictionary: {cryptFilterDictionary}.");
|
||||
}
|
||||
|
||||
var length = default(int?);
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.Length, tokenScanner, out NumericToken lengthToken))
|
||||
if (stringFilterName != NameToken.Identity && !namedFilters.TryGet(stringFilterName, out _))
|
||||
{
|
||||
length = lengthToken.Int;
|
||||
throw new PdfDocumentEncryptedException($"String filter {stringFilterName} not found in crypt dictionary: {cryptFilterDictionary}.");
|
||||
}
|
||||
|
||||
var revision = default(int);
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.R, tokenScanner, out NumericToken revisionToken))
|
||||
{
|
||||
revision = revisionToken.Int;
|
||||
}
|
||||
cryptHandler = new CryptHandler(namedFilters, streamFilterName, stringFilterName);
|
||||
|
||||
encryptionDictionary.TryGetOptionalStringDirect(NameToken.O, tokenScanner, out var ownerString);
|
||||
encryptionDictionary.TryGetOptionalStringDirect(NameToken.U, tokenScanner, out var userString);
|
||||
|
||||
var access = default(UserAccessPermissions);
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.P, tokenScanner, out NumericToken accessToken))
|
||||
{
|
||||
access = (UserAccessPermissions) accessToken.Int;
|
||||
}
|
||||
|
||||
encryptionDictionary.TryGetOptionalTokenDirect(NameToken.EncryptMetaData, tokenScanner, out BooleanToken encryptMetadata);
|
||||
|
||||
return new EncryptionDictionary(filter.Data, code, length, revision, ownerString, userString, access, encryptionDictionary,
|
||||
encryptMetadata?.Data ?? false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A code specifying the algorithm to be used in encrypting and decrypting the document.
|
||||
/// </summary>
|
||||
internal enum EncryptionAlgorithmCode
|
||||
{
|
||||
/// <summary>
|
||||
/// An algorithm that is undocumented and no longer supported.
|
||||
/// </summary>
|
||||
Unrecognized = 0,
|
||||
/// <summary>
|
||||
/// RC4 or AES encryption using a key of 40 bits.
|
||||
/// </summary>
|
||||
Rc4OrAes40BitKey = 1,
|
||||
/// <summary>
|
||||
/// RC4 or AES encryption using a key of more than 40 bits.
|
||||
/// </summary>
|
||||
Rc4OrAesGreaterThan40BitKey = 2,
|
||||
/// <summary>
|
||||
/// An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
|
||||
/// </summary>
|
||||
UnpublishedAlgorithm40To128BitKey = 3,
|
||||
/// <summary>
|
||||
/// The security handler defines the use of encryption and decryption in the document.
|
||||
/// </summary>
|
||||
SecurityHandlerInDocument
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum UserAccessPermissions
|
||||
{
|
||||
/// <summary>
|
||||
/// (Revision 2) Print the document.
|
||||
/// (Revision 3 or greater) Print the document (possibly not at the highest quality level, see <see cref="PrintHighQuality"/>).
|
||||
/// </summary>
|
||||
Print = 1 << 2,
|
||||
/// <summary>
|
||||
/// Modify the contents of the document by operations other than those
|
||||
/// controlled by <see cref="AddOrModifyTextAnnotationsAndFillFormFields"/>, <see cref="FillExistingFormFields"/> and <see cref="AssembleDocument"/>.
|
||||
/// </summary>
|
||||
Modify = 1 << 3,
|
||||
/// <summary>
|
||||
/// (Revision 2) Copy or otherwise extract text and graphics from the document, including extracting text and graphics
|
||||
/// (in support of accessibility to users with disabilities or for other purposes).
|
||||
/// (Revision 3 or greater) Copy or otherwise extract text and graphics from the document by operations other
|
||||
/// than that controlled by <see cref="ExtractTextAndGraphics"/>.
|
||||
/// </summary>
|
||||
CopyTextAndGraphics = 1 << 4,
|
||||
/// <summary>
|
||||
/// Add or modify text annotations, fill in interactive form fields, and, if <see cref="Modify"/> is also set,
|
||||
/// create or modify interactive form fields (including signature fields).
|
||||
/// </summary>
|
||||
AddOrModifyTextAnnotationsAndFillFormFields = 1 << 5,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Fill in existing interactive form fields (including signature fields),
|
||||
/// even if <see cref="AddOrModifyTextAnnotationsAndFillFormFields"/> is clear.
|
||||
/// </summary>
|
||||
FillExistingFormFields = 1 << 8,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).
|
||||
/// </summary>
|
||||
ExtractTextAndGraphics = 1 << 9,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images),
|
||||
/// even if <see cref="Modify"/> is clear.
|
||||
/// </summary>
|
||||
AssembleDocument = 1 << 10,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Print the document to a representation from which a faithful digital copy of the PDF content could be generated.
|
||||
/// When this is clear (and <see cref="Print"/> is set), printing is limited to a low-level representation of the appearance,
|
||||
/// possibly of degraded quality.
|
||||
/// </summary>
|
||||
PrintHighQuality = 1 << 12
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,55 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
using System;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using Util;
|
||||
|
||||
internal static class EncryptionDictionaryFactory
|
||||
{
|
||||
public static EncryptionDictionary Read(DictionaryToken encryptionDictionary, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
if (encryptionDictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(encryptionDictionary));
|
||||
}
|
||||
|
||||
var filter = encryptionDictionary.Get<NameToken>(NameToken.Filter, tokenScanner);
|
||||
|
||||
var code = EncryptionAlgorithmCode.Unrecognized;
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NumericToken vNum))
|
||||
{
|
||||
code = (EncryptionAlgorithmCode) vNum.Int;
|
||||
}
|
||||
|
||||
var length = default(int?);
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.Length, tokenScanner, out NumericToken lengthToken))
|
||||
{
|
||||
length = lengthToken.Int;
|
||||
}
|
||||
|
||||
var revision = default(int);
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.R, tokenScanner, out NumericToken revisionToken))
|
||||
{
|
||||
revision = revisionToken.Int;
|
||||
}
|
||||
|
||||
encryptionDictionary.TryGetOptionalStringDirect(NameToken.O, tokenScanner, out var ownerString);
|
||||
encryptionDictionary.TryGetOptionalStringDirect(NameToken.U, tokenScanner, out var userString);
|
||||
|
||||
var access = default(UserAccessPermissions);
|
||||
|
||||
if (encryptionDictionary.TryGetOptionalTokenDirect(NameToken.P, tokenScanner, out NumericToken accessToken))
|
||||
{
|
||||
access = (UserAccessPermissions) accessToken.Int;
|
||||
}
|
||||
|
||||
encryptionDictionary.TryGetOptionalTokenDirect(NameToken.EncryptMetaData, tokenScanner, out BooleanToken encryptMetadata);
|
||||
|
||||
return new EncryptionDictionary(filter.Data, code, length, revision, ownerString, userString, access, encryptionDictionary,
|
||||
encryptMetadata?.Data ?? true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -30,6 +30,9 @@
|
||||
[CanBeNull]
|
||||
private readonly EncryptionDictionary encryptionDictionary;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly CryptHandler cryptHandler;
|
||||
|
||||
private readonly byte[] encryptionKey;
|
||||
|
||||
private readonly bool useAes;
|
||||
@@ -49,9 +52,18 @@
|
||||
return;
|
||||
}
|
||||
|
||||
useAes = false;
|
||||
|
||||
if (encryptionDictionary.EncryptionAlgorithmCode == EncryptionAlgorithmCode.SecurityHandlerInDocument)
|
||||
{
|
||||
throw new PdfDocumentEncryptedException("Document encrypted with unsupported algorithm.", encryptionDictionary);
|
||||
if (!encryptionDictionary.TryGetCryptHandler(out var cryptHandlerLocal))
|
||||
{
|
||||
throw new PdfDocumentEncryptedException("Document encrypted with security handler in document but no crypt dictionary found.", encryptionDictionary);
|
||||
}
|
||||
|
||||
cryptHandler = cryptHandlerLocal;
|
||||
|
||||
useAes = cryptHandlerLocal?.StreamDictionary?.Name == CryptDictionary.Method.AesV2;
|
||||
}
|
||||
|
||||
var charset = OtherEncodings.Iso88591;
|
||||
@@ -93,19 +105,15 @@
|
||||
throw new PdfDocumentEncryptedException("The document was encrypted and the provided password was neither the user or owner password.", encryptionDictionary);
|
||||
}
|
||||
|
||||
encryptionKey = CalculateKeyRevisions2To4(decryptionPasswordBytes, encryptionDictionary.OwnerBytes, (int)encryptionDictionary.UserAccessPermissions,
|
||||
encryptionDictionary.StandardSecurityHandlerRevision,
|
||||
encryptionKey = CalculateKeyRevisions2To4(decryptionPasswordBytes, encryptionDictionary,
|
||||
length,
|
||||
documentIdBytes);
|
||||
|
||||
useAes = false;
|
||||
}
|
||||
|
||||
private static bool IsUserPassword(byte[] passwordBytes, EncryptionDictionary encryptionDictionary, int length, byte[] documentIdBytes)
|
||||
{
|
||||
// 1. Create an encryption key based on the user password string.
|
||||
var calculatedEncryptionKey = CalculateKeyRevisions2To4(passwordBytes, encryptionDictionary.OwnerBytes, (int)encryptionDictionary.UserAccessPermissions,
|
||||
encryptionDictionary.StandardSecurityHandlerRevision, length, documentIdBytes);
|
||||
var calculatedEncryptionKey = CalculateKeyRevisions2To4(passwordBytes, encryptionDictionary, length, documentIdBytes);
|
||||
|
||||
byte[] output;
|
||||
|
||||
@@ -245,6 +253,13 @@
|
||||
{
|
||||
case StreamToken stream:
|
||||
{
|
||||
if (cryptHandler?.StreamDictionary?.IsIdentity == true
|
||||
|| cryptHandler?.StreamDictionary?.Name == CryptDictionary.Method.None)
|
||||
{
|
||||
// TODO: No idea if this is right.
|
||||
return token;
|
||||
}
|
||||
|
||||
if (stream.StreamDictionary.TryGet(NameToken.Type, out NameToken typeName))
|
||||
{
|
||||
if (NameToken.Xref.Equals(typeName))
|
||||
@@ -270,6 +285,13 @@
|
||||
}
|
||||
case StringToken stringToken:
|
||||
{
|
||||
if (cryptHandler?.StringDictionary?.IsIdentity == true
|
||||
|| cryptHandler?.StringDictionary?.Name == CryptDictionary.Method.None)
|
||||
{
|
||||
// TODO: No idea if this is right.
|
||||
return token;
|
||||
}
|
||||
|
||||
var data = OtherEncodings.StringAsLatin1Bytes(stringToken.Data);
|
||||
|
||||
var decrypted = DecryptData(data, reference);
|
||||
@@ -386,22 +408,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] CalculateKeyRevisions2To4(byte[] password, byte[] ownerKey,
|
||||
int permissions, int revision, int length, byte[] documentId)
|
||||
private static byte[] CalculateKeyRevisions2To4(byte[] password, EncryptionDictionary encryptionDictionary, int length, byte[] documentId)
|
||||
{
|
||||
// 1. Pad or truncate the password string to exactly 32 bytes.
|
||||
var passwordFull = GetPaddedPassword(password);
|
||||
|
||||
var revision = encryptionDictionary.StandardSecurityHandlerRevision;
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
// 2. Initialize the MD5 hash function and pass the result of step 1 as input to this function.
|
||||
UpdateMd5(md5, passwordFull);
|
||||
|
||||
// 3. Pass the value of the encryption dictionary's owner key entry to the MD5 hash function.
|
||||
UpdateMd5(md5, ownerKey);
|
||||
UpdateMd5(md5, encryptionDictionary.OwnerBytes);
|
||||
|
||||
// 4. Treat the value of the P entry as an unsigned 4-byte integer.
|
||||
var unsigned = (uint)permissions;
|
||||
var unsigned = (uint)encryptionDictionary.UserAccessPermissions;
|
||||
|
||||
// 4. Pass these bytes to the MD5 hash function, low-order byte first.
|
||||
UpdateMd5(md5, new[] { (byte)(unsigned) });
|
||||
@@ -414,7 +437,7 @@
|
||||
|
||||
// 6. (Revision 4 or greater) If document metadata is not being encrypted, pass 4 bytes
|
||||
// with the value 0xFFFFFFFF to the MD5 hash function.
|
||||
if (revision >= 4)
|
||||
if (revision >= 4 && !encryptionDictionary.EncryptMetadata)
|
||||
{
|
||||
UpdateMd5(md5, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
|
||||
}
|
||||
@@ -427,15 +450,25 @@
|
||||
{
|
||||
var n = length;
|
||||
|
||||
var input = md5.Hash;
|
||||
md5.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0);
|
||||
|
||||
var input = md5.Hash;
|
||||
using (var newMd5 = MD5.Create())
|
||||
{
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
UpdateMd5(md5, input.Take(n).ToArray());
|
||||
input = md5.Hash;
|
||||
input = newMd5.ComputeHash(input.Take(n).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
var result = new byte[length];
|
||||
|
||||
Array.Copy(input, result, length);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
md5.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0);
|
||||
|
||||
var result = new byte[length];
|
||||
@@ -445,6 +478,7 @@
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateMd5(MD5 md5, byte[] data)
|
||||
{
|
||||
|
51
src/UglyToad.PdfPig/Encryption/UserAccessPermissions.cs
Normal file
51
src/UglyToad.PdfPig/Encryption/UserAccessPermissions.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace UglyToad.PdfPig.Encryption
|
||||
{
|
||||
using System;
|
||||
|
||||
[Flags]
|
||||
internal enum UserAccessPermissions
|
||||
{
|
||||
/// <summary>
|
||||
/// (Revision 2) Print the document.
|
||||
/// (Revision 3 or greater) Print the document (possibly not at the highest quality level, see <see cref="PrintHighQuality"/>).
|
||||
/// </summary>
|
||||
Print = 1 << 2,
|
||||
/// <summary>
|
||||
/// Modify the contents of the document by operations other than those
|
||||
/// controlled by <see cref="AddOrModifyTextAnnotationsAndFillFormFields"/>, <see cref="FillExistingFormFields"/> and <see cref="AssembleDocument"/>.
|
||||
/// </summary>
|
||||
Modify = 1 << 3,
|
||||
/// <summary>
|
||||
/// (Revision 2) Copy or otherwise extract text and graphics from the document, including extracting text and graphics
|
||||
/// (in support of accessibility to users with disabilities or for other purposes).
|
||||
/// (Revision 3 or greater) Copy or otherwise extract text and graphics from the document by operations other
|
||||
/// than that controlled by <see cref="ExtractTextAndGraphics"/>.
|
||||
/// </summary>
|
||||
CopyTextAndGraphics = 1 << 4,
|
||||
/// <summary>
|
||||
/// Add or modify text annotations, fill in interactive form fields, and, if <see cref="Modify"/> is also set,
|
||||
/// create or modify interactive form fields (including signature fields).
|
||||
/// </summary>
|
||||
AddOrModifyTextAnnotationsAndFillFormFields = 1 << 5,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Fill in existing interactive form fields (including signature fields),
|
||||
/// even if <see cref="AddOrModifyTextAnnotationsAndFillFormFields"/> is clear.
|
||||
/// </summary>
|
||||
FillExistingFormFields = 1 << 8,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).
|
||||
/// </summary>
|
||||
ExtractTextAndGraphics = 1 << 9,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images),
|
||||
/// even if <see cref="Modify"/> is clear.
|
||||
/// </summary>
|
||||
AssembleDocument = 1 << 10,
|
||||
/// <summary>
|
||||
/// (Revision 3 or greater) Print the document to a representation from which a faithful digital copy of the PDF content could be generated.
|
||||
/// When this is clear (and <see cref="Print"/> is set), printing is limited to a low-level representation of the appearance,
|
||||
/// possibly of degraded quality.
|
||||
/// </summary>
|
||||
PrintHighQuality = 1 << 12
|
||||
}
|
||||
}
|
@@ -39,6 +39,7 @@
|
||||
public static readonly NameToken Ascii85Decode = new NameToken("ASCII85Decode");
|
||||
public static readonly NameToken Ascii85DecodeAbbreviation = new NameToken("A85");
|
||||
public static readonly NameToken Attached = new NameToken("Attached");
|
||||
public static readonly NameToken AuthEvent = new NameToken("AuthEvent");
|
||||
public static readonly NameToken Author = new NameToken("Author");
|
||||
public static readonly NameToken AvgWidth = new NameToken("AvgWidth");
|
||||
#endregion
|
||||
@@ -126,6 +127,7 @@
|
||||
public static readonly NameToken Creator = new NameToken("Creator");
|
||||
public static readonly NameToken CropBox = new NameToken("CropBox");
|
||||
public static readonly NameToken Crypt = new NameToken("Crypt");
|
||||
public static readonly NameToken CryptFilter = new NameToken("CryptFilter");
|
||||
public static readonly NameToken Cs = new NameToken("CS");
|
||||
// D
|
||||
public static readonly NameToken D = new NameToken("D");
|
||||
@@ -164,6 +166,7 @@
|
||||
public static readonly NameToken Dl = new NameToken("DL");
|
||||
public static readonly NameToken Dm = new NameToken("Dm");
|
||||
public static readonly NameToken Doc = new NameToken("Doc");
|
||||
public static readonly NameToken DocOpen = new NameToken("DocOpen");
|
||||
public static readonly NameToken DocChecksum = new NameToken("DocChecksum");
|
||||
public static readonly NameToken DocTimeStamp = new NameToken("DocTimeStamp");
|
||||
public static readonly NameToken Docmdp = new NameToken("DocMDP");
|
||||
@@ -181,6 +184,7 @@
|
||||
public static readonly NameToken E = new NameToken("E");
|
||||
public static readonly NameToken EarlyChange = new NameToken("EarlyChange");
|
||||
public static readonly NameToken Ef = new NameToken("EF");
|
||||
public static readonly NameToken EfOpen = new NameToken("EFOpen");
|
||||
public static readonly NameToken EmbeddedFdfs = new NameToken("EmbeddedFDFs");
|
||||
public static readonly NameToken EmbeddedFiles = new NameToken("EmbeddedFiles");
|
||||
public static readonly NameToken Empty = new NameToken("");
|
||||
@@ -527,6 +531,7 @@
|
||||
public static readonly NameToken UserUnit = new NameToken("UserUnit");
|
||||
// V
|
||||
public static readonly NameToken V = new NameToken("V");
|
||||
public static readonly NameToken V2 = new NameToken("V2");
|
||||
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
|
||||
public static readonly NameToken Version = new NameToken("Version");
|
||||
public static readonly NameToken Vertices = new NameToken("Vertices");
|
||||
|
Reference in New Issue
Block a user