#region PDFsharp - A .NET library for processing PDF
//
// Authors:
// Stefan Lange
//
// Copyright (c) 2005-2017 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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Internal;
#if !NETFX_CORE && !UWP
using System.Security.Cryptography;
#endif
#pragma warning disable 0169
#pragma warning disable 0649
namespace PdfSharp.Pdf.Security
{
///
/// Represents the standard PDF security handler.
///
public sealed class PdfStandardSecurityHandler : PdfSecurityHandler
{
internal PdfStandardSecurityHandler(PdfDocument document)
: base(document)
{ }
internal PdfStandardSecurityHandler(PdfDictionary dict)
: base(dict)
{ }
///
/// Sets the user password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
///
public string UserPassword
{
set
{
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
_userPassword = value;
}
}
internal string _userPassword;
///
/// Sets the owner password of the document. Setting a password automatically sets the
/// PdfDocumentSecurityLevel to PdfDocumentSecurityLevel.Encrypted128Bit if its current
/// value is PdfDocumentSecurityLevel.None.
///
public string OwnerPassword
{
set
{
if (_document._securitySettings.DocumentSecurityLevel == PdfDocumentSecurityLevel.None)
_document._securitySettings.DocumentSecurityLevel = PdfDocumentSecurityLevel.Encrypted128Bit;
_ownerPassword = value;
}
}
internal string _ownerPassword;
///
/// Gets or sets the user access permission represented as an integer in the P key.
///
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); }
}
///
/// Encrypts the whole document.
///
public void EncryptDocument()
{
foreach (PdfReference iref in _document._irefTable.AllReferences)
{
if (!ReferenceEquals(iref.Value, this))
EncryptObject(iref.Value);
}
}
///
/// Encrypts an indirect object.
///
internal void EncryptObject(PdfObject value)
{
Debug.Assert(value.Reference != null);
SetHashKey(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;
PrepareKey();
EncryptRC4(bytes);
str.EncryptionValue = bytes;
}
}
}
///
/// Encrypts a dictionary.
///
void EncryptDictionary(PdfDictionary dict)
{
PdfName[] names = dict.Elements.KeyNames;
foreach (KeyValuePair 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)
{
PrepareKey();
EncryptRC4(bytes);
dict.Stream.Value = bytes;
}
}
}
///
/// Encrypts an array.
///
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)
EncryptString(value1);
else if ((value2 = item as PdfDictionary) != null)
EncryptDictionary(value2);
else if ((value3 = item as PdfArray) != null)
EncryptArray(value3);
}
}
///
/// Encrypts a string.
///
void EncryptString(PdfString value)
{
if (value.Length != 0)
{
byte[] bytes = value.EncryptionValue;
PrepareKey();
EncryptRC4(bytes);
value.EncryptionValue = bytes;
}
}
///
/// Encrypts an array.
///
internal byte[] EncryptBytes(byte[] bytes)
{
if (bytes != null && bytes.Length != 0)
{
PrepareKey();
EncryptRC4(bytes);
}
return bytes;
}
#region Encryption Algorithms
///
/// Checks the password.
///
/// Password or null if no password is provided.
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 <= 3))
throw new PdfReaderException(PSSR.UnknownEncryption);
byte[] documentID = PdfEncoders.RawEncoding.GetBytes(Owner.Internals.FirstDocumentID);
byte[] oValue = PdfEncoders.RawEncoding.GetBytes(Elements.GetString(Keys.O));
byte[] uValue = PdfEncoders.RawEncoding.GetBytes(Elements.GetString(Keys.U));
int pValue = Elements.GetInteger(Keys.P);
int rValue = Elements.GetInteger(Keys.R);
if (inputPassword == null)
inputPassword = "";
bool strongEncryption = rValue == 3;
int keyLength = strongEncryption ? 16 : 32;
// Try owner password first.
//byte[] password = PdfEncoders.RawEncoding.GetBytes(inputPassword);
InitWithOwnerPassword(documentID, inputPassword, oValue, pValue, strongEncryption);
if (EqualsKey(uValue, keyLength))
{
_document.SecuritySettings._hasOwnerPermissions = true;
return PasswordValidity.OwnerPassword;
}
_document.SecuritySettings._hasOwnerPermissions = false;
// Now try user password.
//password = PdfEncoders.RawEncoding.GetBytes(inputPassword);
InitWithUserPassword(documentID, inputPassword, oValue, pValue, strongEncryption);
if (EqualsKey(uValue, keyLength))
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);
}
///
/// Pads a password to a 32 byte array.
///
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,
};
///
/// Generates the user key based on the padded user password.
///
void InitWithUserPassword(byte[] documentID, string userPassword, byte[] ownerKey, int permissions, bool strongEncryption)
{
InitEncryptionKey(documentID, PadPassword(userPassword), ownerKey, permissions, strongEncryption);
SetupUserKey(documentID);
}
///
/// Generates the user key based on the padded owner password.
///
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);
}
///
/// Computes the padded user password from the padded owner password.
///
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;
}
///
/// Computes the encryption key.
///
void InitEncryptionKey(byte[] documentID, byte[] userPad, byte[] ownerKey, int permissions, bool strongEncryption)
{
//#if !SILVERLIGHT
_ownerKey = ownerKey;
_encryptionKey = new byte[strongEncryption ? 16 : 5];
#if !NETFX_CORE
_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);
//#endif
#endif
}
///
/// Computes the user key.
///
void SetupUserKey(byte[] documentID)
{
#if !NETFX_CORE
//#if !SILVERLIGHT
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);
}
//#endif
#endif
}
///
/// Prepare the encryption key.
///
void PrepareKey()
{
if (_key != null && _keySize > 0) //!!!mod 2017-11-06 Added "if" because PrepareRC4Key fails if _key is null. But _key appears to be always null, so maybe PrepareKey() is obsolete.
PrepareRC4Key(_key, 0, _keySize);
}
///
/// Prepare the encryption key.
///
void PrepareRC4Key(byte[] key)
{
PrepareRC4Key(key, 0, key.Length);
}
///
/// Prepare the encryption key.
///
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;
}
}
///
/// Encrypts the data.
///
// ReSharper disable InconsistentNaming
void EncryptRC4(byte[] data)
// ReSharper restore InconsistentNaming
{
EncryptRC4(data, 0, data.Length, data);
}
///
/// Encrypts the data.
///
// ReSharper disable InconsistentNaming
void EncryptRC4(byte[] data, int offset, int length)
// ReSharper restore InconsistentNaming
{
EncryptRC4(data, offset, length, data);
}
///
/// Encrypts the data.
///
void EncryptRC4(byte[] inputData, byte[] outputData)
{
EncryptRC4(inputData, 0, inputData.Length, outputData);
}
///
/// Encrypts the data.
///
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]);
}
}
///
/// Checks whether the calculated key correct.
///
bool EqualsKey(byte[] value, int length)
{
for (int idx = 0; idx < length; idx++)
{
if (_userKey[idx] != value[idx])
return false;
}
return true;
}
///
/// Set the hash key for the specified object.
///
internal void SetHashKey(PdfObjectID id)
{
#if !NETFX_CORE
//#if !SILVERLIGHT
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;
//#endif
#endif
}
///
/// Prepares the security handler for encrypting the document.
///
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
}
///
/// The global encryption key.
///
byte[] _encryptionKey;
#if !SILVERLIGHT && !UWP
///
/// The message digest algorithm MD5.
///
readonly MD5 _md5 = new MD5CryptoServiceProvider();
#if DEBUG_
readonly MD5Managed _md5M = new MD5Managed();
#endif
#else
readonly MD5Managed _md5 = new MD5Managed();
#endif
#if NETFX_CORE
// readonly MD5Managed _md5 = new MD5Managed();
#endif
///
/// Bytes used for RC4 encryption.
///
readonly byte[] _state = new byte[256];
///
/// The encryption key for the owner.
///
byte[] _ownerKey = new byte[32];
///
/// The encryption key for the user.
///
readonly byte[] _userKey = new byte[32];
///
/// The encryption key for a particular object/generation.
///
byte[] _key;
///
/// The encryption key length for a particular object/generation.
///
int _keySize;
#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
///
/// Predefined keys of this dictionary.
///
internal sealed new class Keys : PdfSecurityHandler.Keys
{
///
/// (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
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string R = "/R";
///
/// (Required) 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.
///
[KeyInfo(KeyType.String | KeyType.Required)]
public const string O = "/O";
///
/// (Required) 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.
///
[KeyInfo(KeyType.String | KeyType.Required)]
public const string U = "/U";
///
/// (Required) A set of flags specifying which operations are permitted when the document
/// is opened with user access.
///
[KeyInfo(KeyType.Integer | KeyType.Required)]
public const string P = "/P";
///
/// (Optional; meaningful only when the value of V is 4; PDF 1.5) Indicates whether
/// the document-level metadata stream is to be encrypted. Applications should respect this value.
/// Default value: true.
///
[KeyInfo(KeyType.Boolean | KeyType.Optional)]
public const string EncryptMetadata = "/EncryptMetadata";
///
/// Gets the KeysMeta for these keys.
///
public static DictionaryMeta Meta
{
get { return _meta ?? (_meta = CreateMeta(typeof(Keys))); }
}
static DictionaryMeta _meta;
}
///
/// Gets the KeysMeta of this dictionary type.
///
internal override DictionaryMeta Meta
{
get { return Keys.Meta; }
}
#endregion
}
}