vendor: import KillerPDF 1.7.5 source (GPL-3.0) as the base for MMD PDF
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
internal class AESEncryptor : RC4Encryptor
|
||||
{
|
||||
public override void InitEncryptionKey(string password)
|
||||
{
|
||||
if (rValue == 5)
|
||||
{
|
||||
InitVersion5(password);
|
||||
return;
|
||||
}
|
||||
if (rValue == 6)
|
||||
{
|
||||
// http://esec-lab.sogeti.com/post/The-undocumented-password-validation-algorithm-of-Adobe-Reader-X
|
||||
InitVersion6(password);
|
||||
return;
|
||||
}
|
||||
base.InitEncryptionKey(password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf Reference 1.7 Extension Level 3, Chapter 3.5.2, Algorithm 3.2a
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
protected void InitVersion5(string password)
|
||||
{
|
||||
var pwdBytes = Encoding.UTF8.GetBytes(password);
|
||||
if (pwdBytes.Length > 127)
|
||||
pwdBytes = pwdBytes.Take(127).ToArray();
|
||||
// split O and U into their components
|
||||
var oHash = new byte[32];
|
||||
var oValidation = new byte[8];
|
||||
var oSalt = new byte[8];
|
||||
var uHash = new byte[32];
|
||||
var uValidation = new byte[8];
|
||||
var uSalt = new byte[8];
|
||||
|
||||
Array.Copy(ownerValue, oHash, 32);
|
||||
Array.Copy(ownerValue, 32, oValidation, 0, 8);
|
||||
Array.Copy(ownerValue, 40, oSalt, 0, 8);
|
||||
Array.Copy(userValue, uHash, 32);
|
||||
Array.Copy(userValue, 32, uValidation, 0, 8);
|
||||
Array.Copy(userValue, 40, uSalt, 0, 8);
|
||||
|
||||
computedOwnerValue = new byte[32];
|
||||
computedUserValue = new byte[32];
|
||||
|
||||
var oKeyBytes = new byte[pwdBytes.Length + 8 + 48];
|
||||
Array.Copy(pwdBytes, oKeyBytes, pwdBytes.Length);
|
||||
Array.Copy(oValidation, 0, oKeyBytes, pwdBytes.Length, 8);
|
||||
Array.Copy(userValue, 0, oKeyBytes, pwdBytes.Length + 8, 48);
|
||||
|
||||
HaveOwnerPermission = PasswordMatchR5(oKeyBytes, ownerValue);
|
||||
if (HaveOwnerPermission)
|
||||
{
|
||||
PasswordValid = true;
|
||||
Array.Copy(ownerValue, computedOwnerValue, 32);
|
||||
CreateEncryptionKeyR5(oeValue, pwdBytes, oSalt, userValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
oKeyBytes = new byte[pwdBytes.Length + 8];
|
||||
Array.Copy(pwdBytes, oKeyBytes, pwdBytes.Length);
|
||||
Array.Copy(uValidation, 0, oKeyBytes, pwdBytes.Length, 8);
|
||||
|
||||
// if the result matches the first 32 bytes of userValue, we have the user password
|
||||
PasswordValid = PasswordMatchR5(oKeyBytes, userValue);
|
||||
if (PasswordValid)
|
||||
{
|
||||
Array.Copy(userValue, computedUserValue, 32);
|
||||
CreateEncryptionKeyR5(ueValue, pwdBytes, uSalt, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateEncryptionKeyR5(byte[] encryptedValue, byte[] password, byte[] salt, byte[] uservalue)
|
||||
{
|
||||
var sha = SHA256.Create();
|
||||
var aes256Cbc = Aes.Create();
|
||||
aes256Cbc.KeySize = 256;
|
||||
aes256Cbc.Mode = CipherMode.CBC;
|
||||
aes256Cbc.Padding = PaddingMode.None;
|
||||
var bufLen = password.Length + salt.Length + (uservalue != null ? 48 : 0);
|
||||
var buf = new byte[bufLen];
|
||||
Array.Copy(password, buf, password.Length);
|
||||
Array.Copy(salt, 0, buf, password.Length, salt.Length);
|
||||
if (uservalue != null)
|
||||
Array.Copy(uservalue, 0, buf, password.Length + salt.Length, 48);
|
||||
var shaKey = sha.ComputeHash(buf);
|
||||
using (var decryptor = aes256Cbc.CreateDecryptor(shaKey, new byte[16]))
|
||||
{
|
||||
using (var ms = new MemoryStream(encryptedValue))
|
||||
{
|
||||
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
encryptionKey = new byte[32];
|
||||
cs.Read(encryptionKey, 0, 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
aes256Cbc.Clear();
|
||||
}
|
||||
|
||||
private void InitVersion6(string password)
|
||||
{
|
||||
// split O and U into their components
|
||||
var oSalt = new byte[8];
|
||||
var uSalt = new byte[8];
|
||||
var uKeySalt = new byte[8];
|
||||
var oKeySalt = new byte[8];
|
||||
var oKey = new byte[48];
|
||||
Array.Copy(ownerValue, 32, oSalt, 0, 8);
|
||||
Array.Copy(userValue, 32, uSalt, 0, 8);
|
||||
Array.Copy(userValue, 40, uKeySalt, 0, 8);
|
||||
Array.Copy(ownerValue, 40, oKeySalt, 0, 8);
|
||||
Array.Copy(userValue, oKey, 48);
|
||||
|
||||
computedUserValue = new byte[32];
|
||||
computedOwnerValue = new byte[32];
|
||||
ValidateVersion6(password, uSalt, null, computedUserValue);
|
||||
ValidateVersion6(password, oSalt, oKey, computedOwnerValue);
|
||||
|
||||
byte[] keyToDecrypt = null;
|
||||
byte[] salt = null;
|
||||
byte[] hashKey = null;
|
||||
if (CompareArrays(computedOwnerValue, ownerValue, 32))
|
||||
{
|
||||
keyToDecrypt = oeValue;
|
||||
salt = oKeySalt;
|
||||
hashKey = oKey;
|
||||
PasswordValid = true;
|
||||
HaveOwnerPermission = true;
|
||||
}
|
||||
else if (CompareArrays(computedUserValue, userValue, 32))
|
||||
{
|
||||
keyToDecrypt = ueValue;
|
||||
salt = uKeySalt;
|
||||
PasswordValid = true;
|
||||
}
|
||||
|
||||
if (keyToDecrypt != null)
|
||||
{
|
||||
encryptionKey = new byte[32];
|
||||
var hash = new byte[32];
|
||||
var iv = new byte[16];
|
||||
ValidateVersion6(password, salt, hashKey, hash);
|
||||
using (var aes256 = Aes.Create())
|
||||
{
|
||||
aes256.KeySize = 256;
|
||||
aes256.Mode = CipherMode.CBC;
|
||||
aes256.Padding = PaddingMode.None;
|
||||
using (var decryptor = aes256.CreateDecryptor(hash, iv))
|
||||
{
|
||||
decryptor.TransformBlock(keyToDecrypt, 0, 32, encryptionKey, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateVersion6(string password, byte[] salt, byte[] ownerKey, byte[] hash)
|
||||
{
|
||||
var data = new byte[(128 + 64 + 48) * 64];
|
||||
var block = new byte[64];
|
||||
var blockSize = 32;
|
||||
var dataLen = 0;
|
||||
int i, j, sum;
|
||||
|
||||
using (var aes128 = Aes.Create())
|
||||
{
|
||||
aes128.BlockSize = 16 * 8;
|
||||
aes128.Mode = CipherMode.CBC;
|
||||
var pwdBytes = Encoding.UTF8.GetBytes(password);
|
||||
var iv = new byte[16];
|
||||
var aesKey = new byte[16];
|
||||
|
||||
/* Step 1: calculate initial data block */
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
sha256.TransformBlock(pwdBytes, 0, pwdBytes.Length, pwdBytes, 0);
|
||||
sha256.TransformBlock(salt, 0, salt.Length, salt, 0);
|
||||
if (ownerKey != null)
|
||||
sha256.TransformBlock(ownerKey, 0, ownerKey.Length, ownerKey, 0);
|
||||
sha256.TransformFinalBlock(salt, 0, 0);
|
||||
Array.Copy(sha256.Hash, block, sha256.HashSize / 8);
|
||||
}
|
||||
for (i = 0; i < 64 || i < data[dataLen * 64 - 1] + 32; i++)
|
||||
{
|
||||
/* Step 2: repeat password and data block 64 times */
|
||||
Array.Copy(pwdBytes, data, pwdBytes.Length);
|
||||
Array.Copy(block, 0, data, pwdBytes.Length, blockSize);
|
||||
if (ownerKey != null)
|
||||
Array.Copy(ownerKey, 0, data, pwdBytes.Length + blockSize, 48);
|
||||
dataLen = pwdBytes.Length + blockSize + (ownerKey != null ? 48 : 0);
|
||||
for (j = 1; j < 64; j++)
|
||||
Array.Copy(data, 0, data, j * dataLen, dataLen);
|
||||
|
||||
/* Step 3: encrypt data using data block as key and iv */
|
||||
Array.Copy(block, 16, iv, 0, 16);
|
||||
Array.Copy(block, 0, aesKey, 0, 16);
|
||||
using (var aesEnc = aes128.CreateEncryptor(aesKey, iv))
|
||||
{
|
||||
aesEnc.TransformBlock(data, 0, dataLen * 64, data, 0);
|
||||
|
||||
/* Step 4: determine SHA-2 hash size for this round */
|
||||
for (j = 0, sum = 0; j < 16; j++)
|
||||
sum += data[j];
|
||||
|
||||
/* Step 5: calculate data block for next round */
|
||||
blockSize = 32 + sum % 3 * 16;
|
||||
HashAlgorithm hashAlg = null;
|
||||
switch (blockSize)
|
||||
{
|
||||
case 32:
|
||||
hashAlg = SHA256.Create();
|
||||
break;
|
||||
case 48:
|
||||
hashAlg = SHA384.Create();
|
||||
break;
|
||||
case 64:
|
||||
hashAlg = SHA512.Create();
|
||||
break;
|
||||
}
|
||||
hashAlg.TransformBlock(data, 0, dataLen * 64, data, 0);
|
||||
hashAlg.TransformFinalBlock(data, 0, 0);
|
||||
Array.Copy(hashAlg.Hash, block, hashAlg.HashSize / 8);
|
||||
hashAlg.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Array.Copy(block, hash, 32);
|
||||
}
|
||||
|
||||
private static bool PasswordMatchR5(byte[] key, byte[] comparand)
|
||||
{
|
||||
var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(key);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
if (hash[i] != comparand[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf Reference 1.7, Chapter 7.6.2, Algorithm #1
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public override void CreateHashKey(PdfObjectID id)
|
||||
{
|
||||
if (rValue >= 5)
|
||||
{
|
||||
if (key == null || key.Length != encryptionKey.Length)
|
||||
key = new byte[encryptionKey.Length];
|
||||
Array.Copy(encryptionKey, key, encryptionKey.Length);
|
||||
return;
|
||||
}
|
||||
var objectId = new byte[5];
|
||||
md5.Initialize();
|
||||
// Split the object number and generation
|
||||
objectId[0] = (byte)id.ObjectNumber;
|
||||
objectId[1] = (byte)(id.ObjectNumber >> 8);
|
||||
objectId[2] = (byte)(id.ObjectNumber >> 16);
|
||||
objectId[3] = (byte)id.GenerationNumber;
|
||||
objectId[4] = (byte)(id.GenerationNumber >> 8);
|
||||
var salt = new byte[] { 0x73, 0x41, 0x6C, 0x54 };
|
||||
var k = new byte[encryptionKey.Length + 9];
|
||||
Array.Copy(encryptionKey, k, encryptionKey.Length);
|
||||
Array.Copy(objectId, 0, k, encryptionKey.Length, objectId.Length);
|
||||
Array.Copy(salt, 0, k, encryptionKey.Length + objectId.Length, salt.Length);
|
||||
key = md5.ComputeHash(k);
|
||||
md5.Initialize();
|
||||
keySize = encryptionKey.Length + 5;
|
||||
if (keySize > 16)
|
||||
keySize = 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a block of data
|
||||
/// </summary>
|
||||
/// <param name="bytes">Bytes to decrypt</param>
|
||||
/// <returns></returns>
|
||||
public override byte[] Encrypt(byte[] bytes)
|
||||
{
|
||||
// first 16 bytes should be an initialization vector for the encryption
|
||||
if (bytes.Length <= 16)
|
||||
return bytes;
|
||||
|
||||
var iv = new byte[16];
|
||||
Array.Copy(bytes, iv, 16);
|
||||
// Pdf Reference 1.7, Section 7.6.2 :
|
||||
// "Strings and streams encrypted with AES shall use a padding scheme that is described in Internet RFC 2898, PKCS #5"
|
||||
var output = new byte[bytes.Length - 16];
|
||||
int dataLength;
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
var decryptor = aes.CreateDecryptor(key, iv);
|
||||
try
|
||||
{
|
||||
var offset = decryptor.TransformBlock(bytes, 16, bytes.Length - 16, output, 0);
|
||||
var suffix = decryptor.TransformFinalBlock(bytes, 0, 0);
|
||||
Array.Copy(suffix, 0, output, offset, suffix.Length);
|
||||
dataLength = offset + suffix.Length;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// return unmodified
|
||||
// (encountered documents that were "partly" encrypted, i.e. everything was encrypted except object-streams)
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
return output.Take(dataLength).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using PdfSharpCore.Pdf.Internal;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
internal abstract class EncryptorBase
|
||||
{
|
||||
protected readonly MD5 md5 = MD5.Create();
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for the owner.
|
||||
/// </summary>
|
||||
protected byte[] ownerKey = new byte[32];
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for the user.
|
||||
/// </summary>
|
||||
protected byte[] userKey = new byte[32];
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for a particular object/generation.
|
||||
/// </summary>
|
||||
protected byte[] key;
|
||||
|
||||
/// <summary>
|
||||
/// The global encryption key.
|
||||
/// </summary>
|
||||
protected byte[] encryptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// The /O value as read from the input document
|
||||
/// </summary>
|
||||
protected byte[] ownerValue;
|
||||
|
||||
/// <summary>
|
||||
/// The /U value as read from the input document
|
||||
/// </summary>
|
||||
protected byte[] userValue;
|
||||
|
||||
protected byte[] computedOwnerValue;
|
||||
|
||||
protected byte[] computedUserValue;
|
||||
|
||||
protected byte[] documentId;
|
||||
|
||||
protected byte[] oeValue;
|
||||
|
||||
protected byte[] ueValue;
|
||||
|
||||
protected byte[] permsValue;
|
||||
|
||||
protected int pValue;
|
||||
|
||||
protected int rValue;
|
||||
|
||||
protected int vValue;
|
||||
|
||||
protected bool encryptMetadata;
|
||||
|
||||
protected PdfDictionary cf;
|
||||
|
||||
protected string stmF;
|
||||
|
||||
protected string strF;
|
||||
|
||||
protected int keyLength;
|
||||
|
||||
protected static readonly byte[] passwordPadding = new byte[] // 32 bytes password padding defined by Adobe
|
||||
{
|
||||
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
|
||||
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key length for a particular object/generation.
|
||||
/// </summary>
|
||||
protected int keySize;
|
||||
|
||||
protected PdfDocument doc;
|
||||
|
||||
protected PdfDictionary encryptionDict;
|
||||
|
||||
public bool PasswordValid { get; protected set; }
|
||||
|
||||
public bool HaveOwnerPermission { get; protected set; }
|
||||
|
||||
public void Initialize(PdfDocument document, PdfDictionary encryptionDictionary)
|
||||
{
|
||||
doc = document;
|
||||
encryptionDict = encryptionDictionary;
|
||||
|
||||
documentId = PdfEncoders.RawEncoding.GetBytes(doc.Internals.FirstDocumentID);
|
||||
ownerValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.O));
|
||||
userValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.U));
|
||||
oeValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.OE));
|
||||
ueValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.UE));
|
||||
permsValue = PdfEncoders.RawEncoding.GetBytes(encryptionDict.Elements.GetString(PdfStandardSecurityHandler.Keys.Perms));
|
||||
pValue = encryptionDict.Elements.GetInteger(PdfStandardSecurityHandler.Keys.P);
|
||||
rValue = encryptionDict.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R);
|
||||
vValue = encryptionDict.Elements.GetInteger(PdfSecurityHandler.Keys.V);
|
||||
encryptMetadata = !encryptionDict.Elements.ContainsKey(PdfStandardSecurityHandler.Keys.EncryptMetadata) || encryptionDict.Elements.GetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata);
|
||||
cf = encryptionDict.Elements.GetDictionary(PdfSecurityHandler.Keys.CF);
|
||||
stmF = encryptionDict.Elements.GetString(PdfSecurityHandler.Keys.StmF);
|
||||
strF = encryptionDict.Elements.GetString(PdfSecurityHandler.Keys.StrF);
|
||||
keyLength = encryptionDict.Elements.GetInteger(PdfSecurityHandler.Keys.Length) / 8; // specified in Bits
|
||||
// Length may be absent, use default of 40 bits (see 7.6.1 Table 20, "V" entry)
|
||||
if (keyLength <= 0)
|
||||
keyLength = 5;
|
||||
keySize = keyLength;
|
||||
}
|
||||
|
||||
public void SetEncryptionKey(byte[] encKey)
|
||||
{
|
||||
encryptionKey = encKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads a password to a 32 byte array.
|
||||
/// </summary>
|
||||
protected static byte[] PadPassword(string password)
|
||||
{
|
||||
var padded = new byte[32];
|
||||
if (password == null)
|
||||
Array.Copy(passwordPadding, 0, padded, 0, 32);
|
||||
else
|
||||
{
|
||||
int length = password.Length;
|
||||
Array.Copy(PdfEncoders.RawEncoding.GetBytes(password), 0, padded, 0, Math.Min(length, 32));
|
||||
if (length < 32)
|
||||
Array.Copy(passwordPadding, 0, padded, length, 32 - length);
|
||||
}
|
||||
return padded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the first 'length' bytes of two byte-arrays
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected static bool CompareArrays(byte[] left, byte[] right, int length)
|
||||
{
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
if (left[i] != right[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using PdfSharpCore.Pdf.IO;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
internal class EncryptorFactory
|
||||
{
|
||||
public static void Create(PdfDocument doc, PdfDictionary dict, out IEncryptor stringEncryptor, out IEncryptor streamEncryptor)
|
||||
{
|
||||
stringEncryptor = streamEncryptor = null;
|
||||
|
||||
var filter = dict.Elements.GetName(PdfSecurityHandler.Keys.Filter);
|
||||
var v = dict.Elements.GetInteger(PdfSecurityHandler.Keys.V);
|
||||
var maxSupportedVersion = 5;
|
||||
if (filter != "/Standard" || !(v >= 1 && v <= maxSupportedVersion))
|
||||
throw new PdfReaderException(PSSR.UnknownEncryption);
|
||||
foreach (var keyName in new []{"/StrF", "/StmF"})
|
||||
{
|
||||
IEncryptor encryptor = null;
|
||||
if (v >= 4)
|
||||
{
|
||||
var cf = dict.Elements.GetDictionary(PdfSecurityHandler.Keys.CF);
|
||||
if (cf != null)
|
||||
{
|
||||
var filterName = dict.Elements.GetName(keyName);
|
||||
if (!string.IsNullOrEmpty(filterName))
|
||||
{
|
||||
/*
|
||||
* Pdf Reference 1.7 Chapter 7.6.5 (Crypt Filters)
|
||||
*
|
||||
* None: The application shall not decrypt data but shall direct the input stream to the security handler for decryption.
|
||||
* V2: The application shall ask the security handler for the encryption key and shall implicitly decrypt data with
|
||||
* "Algorithm 1: Encryption of data using the RC4 or AES algorithms", using the RC4 algorithm.
|
||||
* AESV2: (PDF 1.6)The application shall ask the security handler for the encryption key and shall implicitly decrypt data with
|
||||
* "Algorithm 1: Encryption of data using the RC4 or AES algorithms", using the AES algorithm in Cipher Block
|
||||
* Chaining (CBC) mode with a 16-byte block size and an initialization vector that shall be randomly generated and
|
||||
* placed as the first 16 bytes in the stream or string.
|
||||
* AESV3: (PDF 1.7, ExtensionLevel 3) The application asks the security handler for the encryption key and implicitly decrypts data with
|
||||
* Algorithm 3.1a, using the AES-256 algorithm in Cipher Block Chaining (CBC) with padding 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.
|
||||
* The key size (Length) shall be 256 bits.
|
||||
*/
|
||||
var filterDict = cf.Elements.GetDictionary(filterName);
|
||||
if (filterDict != null)
|
||||
{
|
||||
var cfm = filterDict.Elements.GetName(PdfSecurityHandler.Keys.CFM);
|
||||
if (!string.IsNullOrEmpty(cfm) && cfm.StartsWith("/AESV")) // AESV2(PDF 1.6), AESV3(PDF 1.7, ExtensionLevel 3)
|
||||
encryptor = new AESEncryptor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// default to RC4 encryption
|
||||
if (encryptor == null)
|
||||
encryptor = new RC4Encryptor();
|
||||
encryptor.Initialize(doc, dict);
|
||||
if (keyName == "/StrF")
|
||||
stringEncryptor = encryptor;
|
||||
else
|
||||
streamEncryptor = encryptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
internal interface IEncryptor
|
||||
{
|
||||
bool PasswordValid { get; }
|
||||
|
||||
bool HaveOwnerPermission { get; }
|
||||
|
||||
void Initialize(PdfDocument document, PdfDictionary encryptionDict);
|
||||
|
||||
void InitEncryptionKey(string password);
|
||||
|
||||
bool ValidatePassword(string password);
|
||||
|
||||
void SetEncryptionKey(byte[] key);
|
||||
|
||||
void CreateHashKey(PdfObjectID objectId);
|
||||
|
||||
byte[] Encrypt(byte[] bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
#region PDFsharp - A .NET library for processing PDF
|
||||
//
|
||||
// Authors:
|
||||
// Stefan Lange
|
||||
//
|
||||
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
|
||||
//
|
||||
// http://www.PdfSharp.com
|
||||
// http://sourceforge.net/projects/pdfsharp
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
#endregion
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base of all security handlers.
|
||||
/// </summary>
|
||||
public abstract class PdfSecurityHandler : PdfDictionary
|
||||
{
|
||||
internal PdfSecurityHandler(PdfDocument document)
|
||||
: base(document)
|
||||
{ }
|
||||
|
||||
internal PdfSecurityHandler(PdfDictionary dict)
|
||||
: base(dict)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Predefined keys of this dictionary.
|
||||
/// </summary>
|
||||
internal class Keys : KeysBase
|
||||
{
|
||||
/// <summary>
|
||||
/// (Required) The name of the preferred security handler for this document. Typically,
|
||||
/// it is the name of the security handler that was used to encrypt the document. If
|
||||
/// SubFilter is not present, only this security handler should be used when opening
|
||||
/// the document. If it is present, consumer applications can use any security handler
|
||||
/// that implements the format specified by SubFilter.
|
||||
/// Standard is the name of the built-in password-based security handler. Names for other
|
||||
/// security handlers can be registered by using the procedure described in Appendix E.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Name | KeyType.Required)]
|
||||
public const string Filter = "/Filter";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; PDF 1.3) A name that completely specifies the format and interpretation of
|
||||
/// the contents of the encryption dictionary. It is needed to allow security handlers other
|
||||
/// than the one specified by Filter to decrypt the document. If this entry is absent, other
|
||||
/// security handlers should not be allowed to decrypt the document.
|
||||
/// </summary>
|
||||
[KeyInfo("1.3", KeyType.Name | KeyType.Optional)]
|
||||
public const string SubFilter = "/SubFilter";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional but strongly recommended) A code specifying the algorithm to be used in encrypting
|
||||
/// and decrypting the document:
|
||||
/// 0 An algorithm that is undocumented and no longer supported, and whose use is strongly discouraged.
|
||||
/// 1 Algorithm 3.1, with an encryption key length of 40 bits.
|
||||
/// 2 (PDF 1.4) Algorithm 3.1, but permitting encryption key lengths greater than 40 bits.
|
||||
/// 3 (PDF 1.4) An unpublished algorithm that permits encryption key lengths ranging from 40 to 128 bits.
|
||||
/// 4 (PDF 1.5) The security handler defines the use of encryption and
|
||||
/// decryption in the document, using the rules specified by the CF,
|
||||
/// StmF, and StrF entries using algorithm 3.1 with a key length of 128 bits.
|
||||
/// 5 (ExtensionLevel 3) The security handler defines the use of
|
||||
/// encryption and decryption in the document, using the rules
|
||||
/// specified by the CF, StmF, and StrF entries using algorithm 3.1a with a key length of 256 bits
|
||||
/// The default value if this entry is omitted is 0, but a value of 1 or greater is strongly recommended.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Optional)]
|
||||
public const string V = "/V";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; PDF 1.4; only if V is 2 or 3) The length of the encryption key, in bits.
|
||||
/// The value must be a multiple of 8, in the range 40 to 256. Default value: 40.
|
||||
/// Note: Security handlers can define their own use of the Length entry
|
||||
/// but are encouraged to use it to define the bit length of the encryption key.
|
||||
/// </summary>
|
||||
[KeyInfo("1.4", KeyType.Integer | KeyType.Optional)]
|
||||
public const string Length = "/Length";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; meaningful only when the value of V is 4; PDF 1.5)
|
||||
/// A dictionary whose keys are crypt filter names and whose values are the corresponding
|
||||
/// crypt filter dictionaries. Every crypt filter used in the document must have an entry
|
||||
/// in this dictionary, except for the standard crypt filter names.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Dictionary | KeyType.Optional)]
|
||||
public const string CF = "/CF";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional) The method used, if any, by the consumer application to decrypt data.
|
||||
/// The following values are supported:
|
||||
/// • None The application does not decrypt data but directs the input
|
||||
/// stream to the security handler for decryption. (See implementation
|
||||
/// note 30 in Appendix H.)
|
||||
/// • V2 The application asks the security handler for the encryption key
|
||||
/// and implicitly decrypts data with Algorithm 3.1, using the RC4 algorithm.
|
||||
/// • AESV2(PDF 1.6) The application asks the security handler for the
|
||||
/// encryption key and implicitly decrypts data with Algorithm 3.1, using
|
||||
/// the AES-128 algorithm in Cipher Block Chaining(CBC) with padding
|
||||
/// 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. The key size(Length) shall be 128 bits.
|
||||
/// • AESV3(ExtensionLevel 3) The application asks the security handler
|
||||
/// for the encryption key and implicitly decrypts data with
|
||||
/// Algorithm 3.1a, using the AES-256 algorithm in Cipher Block
|
||||
/// Chaining(CBC) with padding 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. The key size(Length) shall be 256 bits.
|
||||
/// When the value is V2, AESV2, or AESV3, the application may ask once for
|
||||
/// this encryption key and cache the key for subsequent use for streams
|
||||
/// that use the same crypt filter.Therefore, there must be a one-to-one
|
||||
/// relationship between a crypt filter name and the corresponding encryption key.
|
||||
/// Only the values listed here are supported. Applications that encounter
|
||||
/// other values should report that the file is encrypted with an unsupported algorithm.
|
||||
/// Default value: None.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.String | KeyType.Optional)]
|
||||
public const string CFM = "/CFM";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.5)
|
||||
/// The name of the crypt filter that is used by default when decrypting streams.
|
||||
/// The name must be a key in the CF dictionary or a standard crypt filter name. All streams
|
||||
/// in the document, except for cross-reference streams or streams that have a Crypt entry in
|
||||
/// their Filter array, are decrypted by the security handler, using this crypt filter.
|
||||
/// Default value: Identity.
|
||||
/// </summary>
|
||||
[KeyInfo("1.5", KeyType.Name | KeyType.Optional)]
|
||||
public const string StmF = "/StmF";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.)
|
||||
/// The name of the crypt filter that is used when decrypting all strings in the document.
|
||||
/// The name must be a key in the CF dictionary or a standard crypt filter name.
|
||||
/// Default value: Identity.
|
||||
/// </summary>
|
||||
[KeyInfo("1.5", KeyType.Name | KeyType.Optional)]
|
||||
public const string StrF = "/StrF";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.6)
|
||||
/// The name of the crypt filter that should be used by default when encrypting embedded
|
||||
/// file streams; it must correspond to a key in the CF dictionary or a standard crypt
|
||||
/// filter name. This entry is provided by the security handler. Applications should respect
|
||||
/// this value when encrypting embedded files, except for embedded file streams that have
|
||||
/// their own crypt filter specifier. If this entry is not present, and the embedded file
|
||||
/// stream does not contain a crypt filter specifier, the stream should be encrypted using
|
||||
/// the default stream crypt filter specified by StmF.
|
||||
/// </summary>
|
||||
[KeyInfo("1.6", KeyType.Name | KeyType.Optional)]
|
||||
public const string EFF = "/EFF";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
#region PDFsharp - A .NET library for processing PDF
|
||||
//
|
||||
// Authors:
|
||||
// Stefan Lange
|
||||
//
|
||||
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
|
||||
//
|
||||
// http://www.PdfSharpCore.com
|
||||
// http://sourceforge.net/projects/pdfsharp
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates access to the security settings of a PDF document.
|
||||
/// </summary>
|
||||
public sealed class PdfSecuritySettings
|
||||
{
|
||||
internal PdfSecuritySettings(PdfDocument document)
|
||||
{
|
||||
_document = document;
|
||||
}
|
||||
readonly PdfDocument _document;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the granted access to the document is 'owner permission'. Returns true if the document
|
||||
/// is unprotected or was opened with the owner password. Returns false if the document was opened with the
|
||||
/// user password.
|
||||
/// </summary>
|
||||
public bool HasOwnerPermissions
|
||||
{
|
||||
get { return _hasOwnerPermissions; }
|
||||
}
|
||||
internal bool _hasOwnerPermissions = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the document security level. If you set the security level to anything but PdfDocumentSecurityLevel.None
|
||||
/// you must also set a user and/or an owner password. Otherwise saving the document will fail.
|
||||
/// </summary>
|
||||
public PdfDocumentSecurityLevel DocumentSecurityLevel
|
||||
{
|
||||
get { return _documentSecurityLevel; }
|
||||
set { _documentSecurityLevel = value; }
|
||||
}
|
||||
PdfDocumentSecurityLevel _documentSecurityLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the user password of the document. Setting a password automatically sets the
|
||||
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
|
||||
/// value is PdfDocumentSecurityLevel.None.
|
||||
/// </summary>
|
||||
public string UserPassword
|
||||
{
|
||||
set { SecurityHandler.UserPassword = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the owner password of the document. Setting a password automatically sets the
|
||||
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
|
||||
/// value is PdfDocumentSecurityLevel.None.
|
||||
/// </summary>
|
||||
public string OwnerPassword
|
||||
{
|
||||
set { SecurityHandler.OwnerPassword = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the document can be saved.
|
||||
/// </summary>
|
||||
internal bool CanSave(ref string message)
|
||||
{
|
||||
if (_documentSecurityLevel != PdfDocumentSecurityLevel.None)
|
||||
{
|
||||
if (String.IsNullOrEmpty(SecurityHandler._userPassword) && String.IsNullOrEmpty(SecurityHandler._ownerPassword))
|
||||
{
|
||||
message = PSSR.UserOrOwnerPasswordRequired;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Permissions
|
||||
//TODO: Use documentation from our English Acrobat 6.0 version.
|
||||
|
||||
/// <summary>
|
||||
/// Permits printing the document. Should be used in conjunction with PermitFullQualityPrint.
|
||||
/// </summary>
|
||||
public bool PermitPrint
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitPrint) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitPrint;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitPrint;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits modifying the document.
|
||||
/// </summary>
|
||||
public bool PermitModifyDocument
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitModifyDocument) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitModifyDocument;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitModifyDocument;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits content copying or extraction.
|
||||
/// </summary>
|
||||
public bool PermitExtractContent
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitExtractContent) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitExtractContent;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitExtractContent;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits commenting the document.
|
||||
/// </summary>
|
||||
public bool PermitAnnotations
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAnnotations) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitAnnotations;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitAnnotations;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits filling of form fields.
|
||||
/// </summary>
|
||||
public bool PermitFormsFill
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitFormsFill) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitFormsFill;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitFormsFill;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits content extraction for accessibility.
|
||||
/// </summary>
|
||||
public bool PermitAccessibilityExtractContent
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAccessibilityExtractContent) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitAccessibilityExtractContent;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitAccessibilityExtractContent;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits to insert, rotate, or delete pages and create bookmarks or thumbnail images even if
|
||||
/// PermitModifyDocument is not set.
|
||||
/// </summary>
|
||||
public bool PermitAssembleDocument
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitAssembleDocument) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitAssembleDocument;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitAssembleDocument;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permits to print in high quality. insert, rotate, or delete pages and create bookmarks or thumbnail images
|
||||
/// even if PermitModifyDocument is not set.
|
||||
/// </summary>
|
||||
public bool PermitFullQualityPrint
|
||||
{
|
||||
get { return (SecurityHandler.Permission & PdfUserAccessPermission.PermitFullQualityPrint) != 0; }
|
||||
set
|
||||
{
|
||||
PdfUserAccessPermission permission = SecurityHandler.Permission;
|
||||
if (value)
|
||||
permission |= PdfUserAccessPermission.PermitFullQualityPrint;
|
||||
else
|
||||
permission &= ~PdfUserAccessPermission.PermitFullQualityPrint;
|
||||
SecurityHandler.Permission = permission;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// PdfStandardSecurityHandler is the only implemented handler.
|
||||
/// </summary>
|
||||
internal PdfStandardSecurityHandler SecurityHandler
|
||||
{
|
||||
get { return _document._trailer.SecurityHandler; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
#region PDFsharp - A .NET library for processing PDF
|
||||
//
|
||||
// Authors:
|
||||
// Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
|
||||
//
|
||||
// Copyright (c) 2005-2016 empira Software GmbH, Cologne (Germany)
|
||||
//
|
||||
// http://www.pdfsharp.com
|
||||
// http://sourceforge.net/projects/pdfsharp
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using PdfSharpCore.Pdf.IO;
|
||||
using PdfSharpCore.Pdf.Advanced;
|
||||
using PdfSharpCore.Pdf.Internal;
|
||||
|
||||
#pragma warning disable 0169
|
||||
#pragma warning disable 0649
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the standard PDF security handler.
|
||||
/// </summary>
|
||||
public sealed class PdfStandardSecurityHandler : PdfSecurityHandler
|
||||
{
|
||||
internal PdfStandardSecurityHandler(PdfDocument document)
|
||||
: base(document)
|
||||
{ }
|
||||
|
||||
internal PdfStandardSecurityHandler(PdfDictionary dict)
|
||||
: base(dict)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the user password of the document. Setting a password automatically sets the
|
||||
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
|
||||
/// value is PdfDocumentSecurityLevel.None.
|
||||
/// </summary>
|
||||
public string UserPassword
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
|
||||
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
|
||||
_userPassword = value;
|
||||
}
|
||||
}
|
||||
internal string _userPassword;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the owner password of the document. Setting a password automatically sets the
|
||||
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
|
||||
/// value is PdfDocumentSecurityLevel.None.
|
||||
/// </summary>
|
||||
public string OwnerPassword
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
|
||||
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
|
||||
_ownerPassword = value;
|
||||
}
|
||||
}
|
||||
internal string _ownerPassword;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user access permission represented as an integer in the P key.
|
||||
/// </summary>
|
||||
internal PdfUserAccessPermission Permission
|
||||
{
|
||||
get
|
||||
{
|
||||
PdfUserAccessPermission permission = (PdfUserAccessPermission)Elements.GetInteger(Keys.P);
|
||||
if ((int)permission == 0)
|
||||
permission = PdfUserAccessPermission.PermitAll;
|
||||
return permission;
|
||||
}
|
||||
set { Elements.SetInteger(Keys.P, (int)value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the whole document.
|
||||
/// </summary>
|
||||
public void EncryptDocument()
|
||||
{
|
||||
foreach (PdfReference iref in _document._irefTable.AllReferences)
|
||||
{
|
||||
if (!ReferenceEquals(iref.Value, this))
|
||||
EncryptObject(iref.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts an indirect object.
|
||||
/// </summary>
|
||||
internal void EncryptObject(PdfObject value)
|
||||
{
|
||||
Debug.Assert(value.Reference != null);
|
||||
|
||||
stringEncryptor.CreateHashKey(value.ObjectID);
|
||||
#if DEBUG
|
||||
if (value.ObjectID.ObjectNumber == 10)
|
||||
GetType();
|
||||
#endif
|
||||
|
||||
PdfDictionary dict;
|
||||
PdfArray array;
|
||||
PdfStringObject str;
|
||||
if ((dict = value as PdfDictionary) != null)
|
||||
EncryptDictionary(dict);
|
||||
else if ((array = value as PdfArray) != null)
|
||||
EncryptArray(array);
|
||||
else if ((str = value as PdfStringObject) != null)
|
||||
{
|
||||
if (str.Length != 0)
|
||||
{
|
||||
byte[] bytes = str.EncryptionValue;
|
||||
bytes = stringEncryptor.Encrypt(bytes);
|
||||
str.EncryptionValue = bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a dictionary.
|
||||
/// </summary>
|
||||
void EncryptDictionary(PdfDictionary dict)
|
||||
{
|
||||
// Pdf Reference 1.7, Chapter 7.5.8.2: The cross-reference stream shall not be encrypted
|
||||
// Pdf Reference 1.7, Chapter 7.6.1: Strings in the Encryption-Dictionary shall not be encrypted
|
||||
if (dict.Elements.GetName("/Type") == "/XRef"
|
||||
|| dict.ObjectNumber == ObjectNumber)
|
||||
return;
|
||||
|
||||
foreach (KeyValuePair<string, PdfItem> item in dict.Elements)
|
||||
{
|
||||
PdfString value1;
|
||||
PdfDictionary value2;
|
||||
PdfArray value3;
|
||||
if ((value1 = item.Value as PdfString) != null)
|
||||
EncryptString(value1);
|
||||
else if ((value2 = item.Value as PdfDictionary) != null)
|
||||
EncryptDictionary(value2);
|
||||
else if ((value3 = item.Value as PdfArray) != null)
|
||||
EncryptArray(value3);
|
||||
}
|
||||
if (dict.Stream != null)
|
||||
{
|
||||
byte[] bytes = dict.Stream.Value;
|
||||
if (bytes.Length != 0)
|
||||
{
|
||||
streamEncryptor.CreateHashKey(dict.ObjectID);
|
||||
bytes = streamEncryptor.Encrypt(bytes);
|
||||
dict.Stream.Value = bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts an array.
|
||||
/// </summary>
|
||||
void EncryptArray(PdfArray array)
|
||||
{
|
||||
int count = array.Elements.Count;
|
||||
for (int idx = 0; idx < count; idx++)
|
||||
{
|
||||
PdfItem item = array.Elements[idx];
|
||||
PdfString value1;
|
||||
PdfDictionary value2;
|
||||
PdfArray value3;
|
||||
if ((value1 = item as PdfString) != null)
|
||||
{
|
||||
stringEncryptor.CreateHashKey(array.ObjectID);
|
||||
EncryptString(value1);
|
||||
}
|
||||
else if ((value2 = item as PdfDictionary) != null)
|
||||
EncryptDictionary(value2);
|
||||
else if ((value3 = item as PdfArray) != null)
|
||||
EncryptArray(value3);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a string.
|
||||
/// </summary>
|
||||
void EncryptString(PdfString value)
|
||||
{
|
||||
if (value.Length != 0)
|
||||
{
|
||||
byte[] bytes = value.EncryptionValue;
|
||||
bytes = stringEncryptor.Encrypt(bytes);
|
||||
value.EncryptionValue = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts an array.
|
||||
/// </summary>
|
||||
internal byte[] EncryptBytes(byte[] bytes)
|
||||
{
|
||||
if (bytes != null && bytes.Length != 0)
|
||||
{
|
||||
PrepareKey();
|
||||
EncryptRC4(bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
#region Encryption Algorithms
|
||||
|
||||
/// <summary>
|
||||
/// Checks the password.
|
||||
/// </summary>
|
||||
/// <param name="inputPassword">Password or null if no password is provided.</param>
|
||||
public PasswordValidity ValidatePassword(string inputPassword)
|
||||
{
|
||||
// We can handle 40 and 128 bit standard encryption.
|
||||
string filter = Elements.GetName(PdfSecurityHandler.Keys.Filter);
|
||||
int v = Elements.GetInteger(PdfSecurityHandler.Keys.V);
|
||||
if (filter != "/Standard" || !(v >= 1 && v <= 5))
|
||||
throw new PdfReaderException(PSSR.UnknownEncryption);
|
||||
|
||||
|
||||
if (inputPassword == null)
|
||||
inputPassword = "";
|
||||
|
||||
EncryptorFactory.Create(_document, this, out stringEncryptor, out streamEncryptor);
|
||||
stringEncryptor.InitEncryptionKey(inputPassword);
|
||||
streamEncryptor.InitEncryptionKey(inputPassword);
|
||||
|
||||
stringEncryptor.ValidatePassword(inputPassword);
|
||||
|
||||
if (stringEncryptor.PasswordValid && stringEncryptor.HaveOwnerPermission)
|
||||
return PasswordValidity.OwnerPassword;
|
||||
if (stringEncryptor.PasswordValid)
|
||||
return PasswordValidity.UserPassword;
|
||||
return PasswordValidity.Invalid;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
static void DumpBytes(string tag, byte[] bytes)
|
||||
{
|
||||
string dump = tag + ": ";
|
||||
for (int idx = 0; idx < bytes.Length; idx++)
|
||||
dump += String.Format("{0:X2}", bytes[idx]);
|
||||
Debug.WriteLine(dump);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads a password to a 32 byte array.
|
||||
/// </summary>
|
||||
static byte[] PadPassword(string password)
|
||||
{
|
||||
byte[] padded = new byte[32];
|
||||
if (password == null)
|
||||
Array.Copy(PasswordPadding, 0, padded, 0, 32);
|
||||
else
|
||||
{
|
||||
int length = password.Length;
|
||||
Array.Copy(PdfEncoders.RawEncoding.GetBytes(password), 0, padded, 0, Math.Min(length, 32));
|
||||
if (length < 32)
|
||||
Array.Copy(PasswordPadding, 0, padded, length, 32 - length);
|
||||
}
|
||||
return padded;
|
||||
}
|
||||
static readonly byte[] PasswordPadding = // 32 bytes password padding defined by Adobe
|
||||
{
|
||||
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
|
||||
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Generates the user key based on the padded user password.
|
||||
/// </summary>
|
||||
void InitWithUserPassword(byte[] documentID, string userPassword, byte[] ownerKey, int permissions, bool strongEncryption)
|
||||
{
|
||||
InitEncryptionKey(documentID, PadPassword(userPassword), ownerKey, permissions, strongEncryption);
|
||||
SetupUserKey(documentID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the user key based on the padded owner password.
|
||||
/// </summary>
|
||||
void InitWithOwnerPassword(byte[] documentID, string ownerPassword, byte[] ownerKey, int permissions, bool strongEncryption)
|
||||
{
|
||||
byte[] userPad = ComputeOwnerKey(ownerKey, PadPassword(ownerPassword), strongEncryption);
|
||||
InitEncryptionKey(documentID, userPad, ownerKey, permissions, strongEncryption);
|
||||
SetupUserKey(documentID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the padded user password from the padded owner password.
|
||||
/// </summary>
|
||||
byte[] ComputeOwnerKey(byte[] userPad, byte[] ownerPad, bool strongEncryption)
|
||||
{
|
||||
byte[] ownerKey = new byte[32];
|
||||
//#if !SILVERLIGHT
|
||||
byte[] digest = _md5.ComputeHash(ownerPad);
|
||||
if (strongEncryption)
|
||||
{
|
||||
byte[] mkey = new byte[16];
|
||||
// Hash the pad 50 times
|
||||
for (int idx = 0; idx < 50; idx++)
|
||||
digest = _md5.ComputeHash(digest);
|
||||
Array.Copy(userPad, 0, ownerKey, 0, 32);
|
||||
// Encrypt the key
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
for (int j = 0; j < mkey.Length; ++j)
|
||||
mkey[j] = (byte)(digest[j] ^ i);
|
||||
PrepareRC4Key(mkey);
|
||||
EncryptRC4(ownerKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PrepareRC4Key(digest, 0, 5);
|
||||
EncryptRC4(userPad, ownerKey);
|
||||
}
|
||||
//#endif
|
||||
return ownerKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the encryption key.
|
||||
/// </summary>
|
||||
void InitEncryptionKey(byte[] documentID, byte[] userPad, byte[] ownerKey, int permissions, bool strongEncryption)
|
||||
{
|
||||
_ownerKey = ownerKey;
|
||||
_encryptionKey = new byte[strongEncryption ? 16 : 5];
|
||||
|
||||
_md5.Initialize();
|
||||
_md5.TransformBlock(userPad, 0, userPad.Length, userPad, 0);
|
||||
_md5.TransformBlock(ownerKey, 0, ownerKey.Length, ownerKey, 0);
|
||||
|
||||
// Split permission into 4 bytes
|
||||
byte[] permission = new byte[4];
|
||||
permission[0] = (byte)permissions;
|
||||
permission[1] = (byte)(permissions >> 8);
|
||||
permission[2] = (byte)(permissions >> 16);
|
||||
permission[3] = (byte)(permissions >> 24);
|
||||
|
||||
_md5.TransformBlock(permission, 0, 4, permission, 0);
|
||||
_md5.TransformBlock(documentID, 0, documentID.Length, documentID, 0);
|
||||
_md5.TransformFinalBlock(permission, 0, 0);
|
||||
byte[] digest = _md5.Hash;
|
||||
_md5.Initialize();
|
||||
// Create the hash 50 times (only for 128 bit)
|
||||
if (_encryptionKey.Length == 16)
|
||||
{
|
||||
for (int idx = 0; idx < 50; idx++)
|
||||
{
|
||||
digest = _md5.ComputeHash(digest);
|
||||
_md5.Initialize();
|
||||
}
|
||||
}
|
||||
Array.Copy(digest, 0, _encryptionKey, 0, _encryptionKey.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the user key.
|
||||
/// </summary>
|
||||
void SetupUserKey(byte[] documentID)
|
||||
{
|
||||
if (_encryptionKey.Length == 16)
|
||||
{
|
||||
_md5.TransformBlock(PasswordPadding, 0, PasswordPadding.Length, PasswordPadding, 0);
|
||||
_md5.TransformFinalBlock(documentID, 0, documentID.Length);
|
||||
byte[] digest = _md5.Hash;
|
||||
_md5.Initialize();
|
||||
Array.Copy(digest, 0, _userKey, 0, 16);
|
||||
for (int idx = 16; idx < 32; idx++)
|
||||
_userKey[idx] = 0;
|
||||
//Encrypt the key
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
for (int j = 0; j < _encryptionKey.Length; j++)
|
||||
digest[j] = (byte)(_encryptionKey[j] ^ i);
|
||||
PrepareRC4Key(digest, 0, _encryptionKey.Length);
|
||||
EncryptRC4(_userKey, 0, 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PrepareRC4Key(_encryptionKey);
|
||||
EncryptRC4(PasswordPadding, _userKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption key.
|
||||
/// </summary>
|
||||
void PrepareKey()
|
||||
{
|
||||
PrepareRC4Key(_key, 0, _keySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption key.
|
||||
/// </summary>
|
||||
void PrepareRC4Key(byte[] key)
|
||||
{
|
||||
PrepareRC4Key(key, 0, key.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption key.
|
||||
/// </summary>
|
||||
void PrepareRC4Key(byte[] key, int offset, int length)
|
||||
{
|
||||
int idx1 = 0;
|
||||
int idx2 = 0;
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
_state[idx] = (byte)idx;
|
||||
byte tmp;
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
{
|
||||
idx2 = (key[idx1 + offset] + _state[idx] + idx2) & 255;
|
||||
tmp = _state[idx];
|
||||
_state[idx] = _state[idx2];
|
||||
_state[idx2] = tmp;
|
||||
idx1 = (idx1 + 1) % length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
// ReSharper disable InconsistentNaming
|
||||
void EncryptRC4(byte[] data)
|
||||
// ReSharper restore InconsistentNaming
|
||||
{
|
||||
EncryptRC4(data, 0, data.Length, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
void EncryptRC4(byte[] data, int offset, int length)
|
||||
{
|
||||
EncryptRC4(data, offset, length, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
void EncryptRC4(byte[] inputData, byte[] outputData)
|
||||
{
|
||||
EncryptRC4(inputData, 0, inputData.Length, outputData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
void EncryptRC4(byte[] inputData, int offset, int length, byte[] outputData)
|
||||
{
|
||||
length += offset;
|
||||
int x = 0, y = 0;
|
||||
byte b;
|
||||
for (int idx = offset; idx < length; idx++)
|
||||
{
|
||||
x = (x + 1) & 255;
|
||||
y = (_state[x] + y) & 255;
|
||||
b = _state[x];
|
||||
_state[x] = _state[y];
|
||||
_state[y] = b;
|
||||
outputData[idx] = (byte)(inputData[idx] ^ _state[(_state[x] + _state[y]) & 255]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the calculated key correct.
|
||||
/// </summary>
|
||||
bool EqualsKey(byte[] value, int length)
|
||||
{
|
||||
for (int idx = 0; idx < length; idx++)
|
||||
{
|
||||
if (_userKey[idx] != value[idx])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the hash key for the specified object.
|
||||
/// </summary>
|
||||
internal void SetHashKey(PdfObjectID id)
|
||||
{
|
||||
byte[] objectId = new byte[5];
|
||||
_md5.Initialize();
|
||||
// Split the object number and generation
|
||||
objectId[0] = (byte)id.ObjectNumber;
|
||||
objectId[1] = (byte)(id.ObjectNumber >> 8);
|
||||
objectId[2] = (byte)(id.ObjectNumber >> 16);
|
||||
objectId[3] = (byte)id.GenerationNumber;
|
||||
objectId[4] = (byte)(id.GenerationNumber >> 8);
|
||||
_md5.TransformBlock(_encryptionKey, 0, _encryptionKey.Length, _encryptionKey, 0);
|
||||
_md5.TransformFinalBlock(objectId, 0, objectId.Length);
|
||||
_key = _md5.Hash;
|
||||
_md5.Initialize();
|
||||
_keySize = _encryptionKey.Length + 5;
|
||||
if (_keySize > 16)
|
||||
_keySize = 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the security handler for encrypting the document.
|
||||
/// </summary>
|
||||
public void PrepareEncryption()
|
||||
{
|
||||
//#if !SILVERLIGHT
|
||||
Debug.Assert(_document._securitySettings.DocumentSecurityLevel != PdfDocumentSecurityLevel.None);
|
||||
int permissions = (int)Permission;
|
||||
bool strongEncryption = _document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.Encrypted128Bit;
|
||||
|
||||
PdfInteger vValue;
|
||||
PdfInteger length;
|
||||
PdfInteger rValue;
|
||||
|
||||
if (strongEncryption)
|
||||
{
|
||||
vValue = new PdfInteger(2);
|
||||
length = new PdfInteger(128);
|
||||
rValue = new PdfInteger(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
vValue = new PdfInteger(1);
|
||||
length = new PdfInteger(40);
|
||||
rValue = new PdfInteger(2);
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(_userPassword))
|
||||
_userPassword = "";
|
||||
// Use user password twice if no owner password provided.
|
||||
if (String.IsNullOrEmpty(_ownerPassword))
|
||||
_ownerPassword = _userPassword;
|
||||
|
||||
// Correct permission bits
|
||||
permissions |= (int)(strongEncryption ? (uint)0xfffff0c0 : (uint)0xffffffc0);
|
||||
permissions &= unchecked((int)0xfffffffc);
|
||||
|
||||
PdfInteger pValue = new PdfInteger(permissions);
|
||||
|
||||
Debug.Assert(_ownerPassword.Length > 0, "Empty owner password.");
|
||||
byte[] userPad = PadPassword(_userPassword);
|
||||
byte[] ownerPad = PadPassword(_ownerPassword);
|
||||
|
||||
_md5.Initialize();
|
||||
_ownerKey = ComputeOwnerKey(userPad, ownerPad, strongEncryption);
|
||||
byte[] documentID = PdfEncoders.RawEncoding.GetBytes(_document.Internals.FirstDocumentID);
|
||||
InitWithUserPassword(documentID, _userPassword, _ownerKey, permissions, strongEncryption);
|
||||
|
||||
PdfString oValue = new PdfString(PdfEncoders.RawEncoding.GetString(_ownerKey, 0, _ownerKey.Length));
|
||||
PdfString uValue = new PdfString(PdfEncoders.RawEncoding.GetString(_userKey, 0, _userKey.Length));
|
||||
|
||||
Elements[Keys.Filter] = new PdfName("/Standard");
|
||||
Elements[Keys.V] = vValue;
|
||||
Elements[Keys.Length] = length;
|
||||
Elements[Keys.R] = rValue;
|
||||
Elements[Keys.O] = oValue;
|
||||
Elements[Keys.U] = uValue;
|
||||
Elements[Keys.P] = pValue;
|
||||
//#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The global encryption key.
|
||||
/// </summary>
|
||||
byte[] _encryptionKey;
|
||||
|
||||
readonly MD5 _md5 = MD5.Create();
|
||||
/// <summary>
|
||||
/// Bytes used for RC4 encryption.
|
||||
/// </summary>
|
||||
readonly byte[] _state = new byte[256];
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for the owner.
|
||||
/// </summary>
|
||||
byte[] _ownerKey = new byte[32];
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for the user.
|
||||
/// </summary>
|
||||
readonly byte[] _userKey = new byte[32];
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key for a particular object/generation.
|
||||
/// </summary>
|
||||
byte[] _key;
|
||||
|
||||
/// <summary>
|
||||
/// The encryption key length for a particular object/generation.
|
||||
/// </summary>
|
||||
int _keySize;
|
||||
|
||||
private IEncryptor stringEncryptor;
|
||||
|
||||
private IEncryptor streamEncryptor;
|
||||
|
||||
#endregion
|
||||
|
||||
internal override void WriteObject(PdfWriter writer)
|
||||
{
|
||||
// Don't encrypt myself.
|
||||
PdfStandardSecurityHandler securityHandler = writer.SecurityHandler;
|
||||
writer.SecurityHandler = null;
|
||||
base.WriteObject(writer);
|
||||
writer.SecurityHandler = securityHandler;
|
||||
}
|
||||
|
||||
#region Keys
|
||||
/// <summary>
|
||||
/// Predefined keys of this dictionary.
|
||||
/// </summary>
|
||||
internal sealed new class Keys : PdfSecurityHandler.Keys
|
||||
{
|
||||
/// <summary>
|
||||
/// (Required) A number specifying which revision of the standard security handler
|
||||
/// should be used to interpret this dictionary:
|
||||
/// • 2 if the document is encrypted with a V value less than 2 and does not have any of
|
||||
/// the access permissions set (by means of the P entry, below) that are designated
|
||||
/// "Revision 3 or greater".
|
||||
/// • 3 if the document is encrypted with a V value of 2 or 3, or has any "Revision 3 or
|
||||
/// greater" access permissions set.
|
||||
/// • 4 if the document is encrypted with a V value of 4
|
||||
/// • 5 (ExtensionLevel 3) if the document is encrypted with a V value of 5
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Required)]
|
||||
public const string R = "/R";
|
||||
|
||||
/// <summary>
|
||||
/// (Required) A string used in computing the encryption key.
|
||||
/// The value of the string depends on the value of the
|
||||
/// revision number, the R entry described above.
|
||||
/// • The value of R is 4 or less: A 32-byte string, based on both the owner and user passwords, that is used in
|
||||
/// computing the encryption key and in determining whether a valid owner password was entered.
|
||||
/// • The value for R is 5: (ExtensionLevel 3) A 48-byte string, based on the owner and user passwords, that is used in
|
||||
/// computing the encryption key and in determining whether a valid owner password was entered.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.String | KeyType.Required)]
|
||||
public const string O = "/O";
|
||||
|
||||
/// <summary>
|
||||
/// (Required) A string based on the user password. The value
|
||||
/// of the string depends on the value of the revision number, the R entry described above.
|
||||
/// • The value of R is 4 or less: A 32-byte string, based on the user password, that is used in determining
|
||||
/// whether to prompt the user for a password and, if so, whether a valid user or owner password was entered.
|
||||
/// • The value for R is 5: (ExtensionLevel 3) A 48-byte string, based on the user password, that is used in
|
||||
/// determining whether to prompt the user for a password and, if so, whether a valid user password was entered.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.String | KeyType.Required)]
|
||||
public const string U = "/U";
|
||||
|
||||
/// <summary>
|
||||
/// (Required) A set of flags specifying which operations are permitted when the document
|
||||
/// is opened with user access.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Required)]
|
||||
public const string P = "/P";
|
||||
|
||||
/// <summary>
|
||||
/// (ExtensionLevel 3; required if R is 5)
|
||||
/// A 32-byte string, based on the owner and user passwords, that is used in computing the encryption key.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Optional)]
|
||||
public const string OE = "/OE";
|
||||
|
||||
/// <summary>
|
||||
/// (ExtensionLevel 3; required if R is 5)
|
||||
/// A 32-byte string, based on the user password, that is used in computing the encryption key.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Optional)]
|
||||
public const string UE = "/UE";
|
||||
|
||||
/// <summary>
|
||||
/// (ExtensionLevel 3; required if R is 5)
|
||||
/// A 16-byte string, encrypted with the file encryption key, that contains an encrypted copy of the permission flags.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Integer | KeyType.Optional)]
|
||||
public const string Perms = "/Perms";
|
||||
|
||||
/// <summary>
|
||||
/// (Optional; meaningful only when the value of V is 4 or 5; PDF 1.5) Indicates whether
|
||||
/// the document-level metadata stream is to be encrypted. Applications should respect this value.
|
||||
/// Default value: true.
|
||||
/// </summary>
|
||||
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
|
||||
public const string EncryptMetadata = "/EncryptMetadata";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the KeysMeta for these keys.
|
||||
/// </summary>
|
||||
public static DictionaryMeta Meta
|
||||
{
|
||||
get { return _meta ?? (_meta = CreateMeta(typeof(Keys))); }
|
||||
}
|
||||
static DictionaryMeta _meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the KeysMeta of this dictionary type.
|
||||
/// </summary>
|
||||
internal override DictionaryMeta Meta
|
||||
{
|
||||
get { return Keys.Meta; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using PdfSharpCore.Pdf.Internal;
|
||||
using System;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
class RC4Encryptor : EncryptorBase, IEncryptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Bytes used for RC4 encryption.
|
||||
/// </summary>
|
||||
readonly byte[] state = new byte[256];
|
||||
|
||||
/// <summary>
|
||||
/// Creates the encryption Key.
|
||||
/// Based on algorithm #2 (3.2 in Extension Level 3) in the Pdf 1.7 reference (7.6.3.3)
|
||||
/// </summary>
|
||||
public virtual void InitEncryptionKey(string password)
|
||||
{
|
||||
var userPad = PadPassword(password);
|
||||
md5.Initialize();
|
||||
md5.TransformBlock(userPad, 0, userPad.Length, userPad, 0);
|
||||
md5.TransformBlock(ownerValue, 0, ownerValue.Length, ownerValue, 0);
|
||||
var permission = new byte[4];
|
||||
permission[0] = (byte)pValue;
|
||||
permission[1] = (byte)(pValue >> 8);
|
||||
permission[2] = (byte)(pValue >> 16);
|
||||
permission[3] = (byte)(pValue >> 24);
|
||||
md5.TransformBlock(permission, 0, 4, permission, 0);
|
||||
md5.TransformBlock(documentId, 0, documentId.Length, documentId, 0);
|
||||
if (rValue >= 4 && !encryptMetadata)
|
||||
{
|
||||
var ff = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||||
md5.TransformBlock(ff, 0, ff.Length, ff, 0);
|
||||
}
|
||||
md5.TransformFinalBlock(permission, 0, 0);
|
||||
var hash = md5.Hash;
|
||||
if (rValue >= 3)
|
||||
{
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
md5.Initialize();
|
||||
hash = md5.ComputeHash(hash, 0, keyLength);
|
||||
}
|
||||
}
|
||||
encryptionKey = new byte[keyLength];
|
||||
Array.Copy(hash, encryptionKey, keyLength);
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string password)
|
||||
{
|
||||
// AESEncryptor sets these
|
||||
if (HaveOwnerPermission || PasswordValid)
|
||||
return true;
|
||||
|
||||
ValidateOwnerPassword(password);
|
||||
if (!PasswordValid)
|
||||
ValidateUserPassword(password);
|
||||
return PasswordValid;
|
||||
|
||||
}
|
||||
|
||||
private void ValidateUserPassword(string password)
|
||||
{
|
||||
CreateUserKey(password);
|
||||
PasswordValid = CompareArrays(computedUserValue, userValue, 16);
|
||||
}
|
||||
|
||||
private void ValidateOwnerPassword(string password)
|
||||
{
|
||||
var pwdPad = PadPassword(password);
|
||||
md5.Initialize();
|
||||
var pwdKey = md5.ComputeHash(pwdPad);
|
||||
if (rValue >= 3)
|
||||
{
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
pwdKey = md5.ComputeHash(pwdKey, 0, keyLength);
|
||||
}
|
||||
}
|
||||
var n = rValue <= 2 ? 5 : keyLength;
|
||||
var rc4Input = new byte[n];
|
||||
Array.Copy(pwdKey, rc4Input, n);
|
||||
|
||||
var ov = new byte[ownerValue.Length];
|
||||
Array.Copy(ownerValue, ov, ov.Length);
|
||||
if (rValue < 3)
|
||||
{
|
||||
PrepareRC4Key(rc4Input, 0, n);
|
||||
EncryptRC4(ov);
|
||||
}
|
||||
else
|
||||
{
|
||||
var xor = new byte[n];
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
for (var j = 0; j < n; j++)
|
||||
xor[j] = (byte)(rc4Input[j] ^ (19 - i));
|
||||
PrepareRC4Key(xor, 0, n);
|
||||
EncryptRC4(ov);
|
||||
}
|
||||
}
|
||||
var userPass = PdfEncoders.RawEncoding.GetString(ov);
|
||||
ValidateUserPassword(userPass);
|
||||
if (PasswordValid)
|
||||
HaveOwnerPermission = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #3
|
||||
/// </summary>
|
||||
public void CreateOwnerKey(string password)
|
||||
{
|
||||
var pwdPad = PadPassword(password);
|
||||
md5.Initialize();
|
||||
var pwdKey = md5.ComputeHash(pwdPad);
|
||||
if (rValue >= 3)
|
||||
{
|
||||
for (var i = 0; i < 50; i++)
|
||||
pwdKey = md5.ComputeHash(pwdKey);
|
||||
}
|
||||
var n = rValue <= 2 ? 5 : keyLength;
|
||||
var rc4Input = new byte[n];
|
||||
Array.Copy(pwdKey, rc4Input, n);
|
||||
if (rValue >= 3)
|
||||
{
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
for (var j = 0; j < rc4Input.Length; j++)
|
||||
rc4Input[j] = (byte)(rc4Input[j] ^ i);
|
||||
PrepareRC4Key(rc4Input);
|
||||
EncryptRC4(pwdPad);
|
||||
}
|
||||
}
|
||||
computedOwnerValue = new byte[n];
|
||||
Array.Copy(pwdPad, computedOwnerValue, n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #4 and #5
|
||||
/// </summary>
|
||||
public void CreateUserKey(string password)
|
||||
{
|
||||
InitEncryptionKey(password);
|
||||
if (rValue == 2)
|
||||
{
|
||||
var data = new byte[passwordPadding.Length];
|
||||
Array.Copy(passwordPadding, data, data.Length);
|
||||
PrepareRC4Key(encryptionKey);
|
||||
EncryptRC4(data);
|
||||
computedUserValue = new byte[data.Length];
|
||||
Array.Copy(data, computedUserValue, data.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
computedUserValue = new byte[32];
|
||||
md5.Initialize();
|
||||
md5.TransformBlock(passwordPadding, 0, passwordPadding.Length, passwordPadding, 0);
|
||||
md5.TransformFinalBlock(documentId, 0, documentId.Length);
|
||||
var mkey = md5.Hash;
|
||||
Array.Copy(mkey, computedUserValue, mkey.Length);
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
for (var j = 0; j < mkey.Length; j++)
|
||||
mkey[j] = (byte)(encryptionKey[j] ^ i);
|
||||
PrepareRC4Key(mkey);
|
||||
EncryptRC4(computedUserValue, 0, 16);
|
||||
}
|
||||
for (var i = 16; i < 32; i++)
|
||||
computedUserValue[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf Reference 1.7, Chapter 7.6.2, Algorithm #1
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public virtual void CreateHashKey(PdfObjectID id)
|
||||
{
|
||||
var objectId = new byte[5];
|
||||
md5.Initialize();
|
||||
// Split the object number and generation
|
||||
objectId[0] = (byte)id.ObjectNumber;
|
||||
objectId[1] = (byte)(id.ObjectNumber >> 8);
|
||||
objectId[2] = (byte)(id.ObjectNumber >> 16);
|
||||
objectId[3] = (byte)id.GenerationNumber;
|
||||
objectId[4] = (byte)(id.GenerationNumber >> 8);
|
||||
md5.TransformBlock(encryptionKey, 0, encryptionKey.Length, encryptionKey, 0); // ?? incomplete
|
||||
md5.TransformFinalBlock(objectId, 0, objectId.Length);
|
||||
key = md5.Hash;
|
||||
md5.Initialize();
|
||||
keySize = encryptionKey.Length + 5;
|
||||
if (keySize > 16)
|
||||
keySize = 16;
|
||||
}
|
||||
|
||||
public virtual byte[] Encrypt(byte[] bytes)
|
||||
{
|
||||
PrepareRC4Key(key);
|
||||
EncryptRC4(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption key.
|
||||
/// </summary>
|
||||
protected void PrepareRC4Key(byte[] key)
|
||||
{
|
||||
PrepareRC4Key(key, 0, keySize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the encryption key.
|
||||
/// </summary>
|
||||
protected void PrepareRC4Key(byte[] key, int offset, int length)
|
||||
{
|
||||
int idx1 = 0;
|
||||
int idx2 = 0;
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
this.state[idx] = (byte)idx;
|
||||
byte tmp;
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
{
|
||||
idx2 = (key[idx1 + offset] + this.state[idx] + idx2) & 255;
|
||||
tmp = this.state[idx];
|
||||
this.state[idx] = this.state[idx2];
|
||||
this.state[idx2] = tmp;
|
||||
idx1 = (idx1 + 1) % length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
protected void EncryptRC4(byte[] data)
|
||||
{
|
||||
EncryptRC4(data, 0, data.Length, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
protected void EncryptRC4(byte[] data, int offset, int length)
|
||||
{
|
||||
EncryptRC4(data, offset, length, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
protected void EncryptRC4(byte[] inputData, byte[] outputData)
|
||||
{
|
||||
EncryptRC4(inputData, 0, inputData.Length, outputData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts the data.
|
||||
/// </summary>
|
||||
protected void EncryptRC4(byte[] inputData, int offset, int length, byte[] outputData)
|
||||
{
|
||||
length += offset;
|
||||
int x = 0, y = 0;
|
||||
byte b;
|
||||
for (int idx = offset; idx < length; idx++)
|
||||
{
|
||||
x = (x + 1) & 255;
|
||||
y = (this.state[x] + y) & 255;
|
||||
b = this.state[x];
|
||||
this.state[x] = this.state[y];
|
||||
this.state[y] = b;
|
||||
outputData[idx] = (byte)(inputData[idx] ^ state[(this.state[x] + this.state[y]) & 255]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#region PDFsharp - A .NET library for processing PDF
|
||||
//
|
||||
// Authors:
|
||||
// Stefan Lange
|
||||
//
|
||||
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
|
||||
//
|
||||
// http://www.PdfSharpCore.com
|
||||
// http://sourceforge.net/projects/pdfsharp
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
#endregion
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the security level of the PDF document.
|
||||
/// </summary>
|
||||
public enum PdfDocumentSecurityLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Document is not protected.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Document is protected with 40-bit security. This option is for compatibility with
|
||||
/// Acrobat 3 and 4 only. Use Encrypted128Bit whenever possible.
|
||||
/// </summary>
|
||||
Encrypted40Bit,
|
||||
|
||||
/// <summary>
|
||||
/// Document is protected with 128-bit security.
|
||||
/// </summary>
|
||||
Encrypted128Bit,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#region PDFsharp - A .NET library for processing PDF
|
||||
//
|
||||
// Authors:
|
||||
// Stefan Lange
|
||||
//
|
||||
// Copyright (c) 2005-2016 empira Software GmbH, Cologne Area (Germany)
|
||||
//
|
||||
// http://www.PdfSharpCore.com
|
||||
// http://sourceforge.net/projects/pdfsharp
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace PdfSharpCore.Pdf.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies which operations are permitted when the document is opened with user access.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
internal enum PdfUserAccessPermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Permits everything. This is the default value.
|
||||
/// </summary>
|
||||
PermitAll = -3, // = 0xFFFFFFFC,
|
||||
|
||||
// Bit 1–2 Reserved; must be 0.
|
||||
|
||||
// Bit 3 (Revision 2) Print the document.
|
||||
// (Revision 3 or greater) Print the document (possibly not at the highest
|
||||
// quality level, depending on whether bit 12 is also set).
|
||||
PermitPrint = 0x00000004, //1 << (3 - 1),
|
||||
|
||||
// Bit 4 Modify the contents of the document by operations other than
|
||||
// those controlled by bits 6, 9, and 11.
|
||||
PermitModifyDocument = 0x00000008, //1 << (4 - 1),
|
||||
|
||||
// Bit 5 (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 bit 10.
|
||||
PermitExtractContent = 0x00000010, //1 << (5 - 1),
|
||||
|
||||
// Bit 6 Add or modify text annotations, fill in interactive form fields, and,
|
||||
// if bit 4 is also set, create or modify interactive form fields (including
|
||||
// signature fields).
|
||||
PermitAnnotations = 0x00000020, //1 << (6 - 1),
|
||||
|
||||
// Bit 7–8 Reserved; must be 1.
|
||||
|
||||
// 9 (Revision 3 or greater) Fill in existing interactive form fields (including
|
||||
// signature fields), even if bit 6 is clear.
|
||||
PermitFormsFill = 0x00000100, //1 << (9 - 1),
|
||||
|
||||
// Bit 10 (Revision 3 or greater) Extract text and graphics (in support of accessibility
|
||||
// to users with disabilities or for other purposes).
|
||||
PermitAccessibilityExtractContent = 0x00000200, //1 << (10 - 1),
|
||||
|
||||
// Bit 11 (Revision 3 or greater) Assemble the document (insert, rotate, or delete
|
||||
// pages and create bookmarks or thumbnail images), even if bit 4
|
||||
// is clear.
|
||||
PermitAssembleDocument = 0x00000400, //1 << (11 - 1),
|
||||
|
||||
// Bit 12 (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 bit is clear (and bit 3 is set), printing is limited to a lowlevel
|
||||
// representation of the appearance, possibly of degraded quality.
|
||||
// (See implementation note 24 in Appendix H.)
|
||||
PermitFullQualityPrint = 0x00000800, //1 << (12 - 1),
|
||||
|
||||
//Bit 13–32 (Revision 3 or greater) Reserved; must be 1.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user