#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 } }