groege / PdfSharpCore

PdfSharp port NetCore
47 stars 13 forks source link

Null Reference exception when Encrypting a PDF document #7

Open elroyheynes opened 7 years ago

elroyheynes commented 7 years ago

PfdSharpCore throws a SystemNullReferenceException when I try to render a document that has had encryption enabled via the PdfSecuritySettings.DocumentSecurityLevel setting.

Stack trace points at PdfSharpCore.Pdf.Security.PdfStandardSecurityHandler.ComputeOwnerKey(Byte[] userPad, Byte[] ownerPad, Boolean strongEncryption) for 128 bit encryption, and PdfSharpCore.Pdf.Security.PdfStandardSecurityHandler.PrepareRC4Key(Byte[] key, Int32 offset, Int32 length) for 40 bit encryption (1 level deeper).

Project is a UWP application targeting Windows 10.

Is there any pre-configuration that is required (similar to Fonts) for document encryption, or is this an issue that merits investigation?

Thanks.

groege commented 7 years ago

This is probably an issue, since I didn't try this before. I have no idea if this is implemented properly. Will look into it withing the next couple of weeks.

groege commented 7 years ago

Actually someone pretty much copied this project XD. They found the same issue: https://github.com/Didstopia/PDFSharp/issues/3 Let's see how they solve it ^^

highfield commented 6 years ago

I have a working solution (although I'm not involved in the spinned-off Didstopia project). I simply surrogated the HashAlgorithm class, because the existent MD5 calculation is not working. Here is the code to replace the existent:

    abstract class HashAlgorithm
    {
        private bool _disposed;
        protected int HashSizeValue;
        protected internal byte[] HashValue;
        protected int State = 0;

        public virtual int HashSize => HashSizeValue;

        public virtual byte[] Hash
        {
            get
            {
                if (_disposed)
                    throw new ObjectDisposedException(null);
                if (State != 0)
                    throw new InvalidOperationException("Hash not yet finalized.");

                return (byte[])HashValue.Clone();
            }
        }

        public byte[] ComputeHash(byte[] buffer)
        {
            if (_disposed)
                throw new ObjectDisposedException(null);
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));

            HashCore(buffer, 0, buffer.Length);
            return CaptureHashCodeAndReinitialize();
        }

        public byte[] ComputeHash(byte[] buffer, int offset, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));
            if (offset < 0)
                throw new ArgumentOutOfRangeException(nameof(offset), "Must be non-negative.");
            if (count < 0 || (count > buffer.Length))
                throw new ArgumentOutOfRangeException(nameof(count), "Invalid value.");
            if ((buffer.Length - count) < offset)
                throw new ArgumentOutOfRangeException(nameof(offset), "Invalid value.");

            if (_disposed)
                throw new ObjectDisposedException(null);

            HashCore(buffer, offset, count);
            return CaptureHashCodeAndReinitialize();
        }

        private byte[] CaptureHashCodeAndReinitialize()
        {
            HashValue = HashFinal();

            // Clone the hash value prior to invoking Initialize in case the user-defined Initialize
            // manipulates the array.
            byte[] tmp = (byte[])HashValue.Clone();
            Initialize();
            return tmp;
        }

        public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);

            // Change the State value
            State = 1;

            HashCore(inputBuffer, inputOffset, inputCount);
            if ((outputBuffer != null) && ((inputBuffer != outputBuffer) || (inputOffset != outputOffset)))
            {
                // We let BlockCopy do the destination array validation
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
            }
            return inputCount;
        }

        public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            ValidateTransformBlock(inputBuffer, inputOffset, inputCount);

            HashCore(inputBuffer, inputOffset, inputCount);
            HashValue = CaptureHashCodeAndReinitialize();
            byte[] outputBytes;
            if (inputCount != 0)
            {
                outputBytes = new byte[inputCount];
                Buffer.BlockCopy(inputBuffer, inputOffset, outputBytes, 0, inputCount);
            }
            else
            {
                outputBytes = Array.Empty<byte>();
            }

            // Reset the State value
            State = 0;

            return outputBytes;
        }

        private void ValidateTransformBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            if (inputBuffer == null)
                throw new ArgumentNullException(nameof(inputBuffer));
            if (inputOffset < 0)
                throw new ArgumentOutOfRangeException(nameof(inputOffset), "Must be non-negative.");
            if (inputCount < 0 || inputCount > inputBuffer.Length)
                throw new ArgumentOutOfRangeException(nameof(inputCount), "Invalid value.");
            if ((inputBuffer.Length - inputCount) < inputOffset)
                throw new ArgumentOutOfRangeException(nameof(inputOffset), "Invalid value.");

            if (_disposed)
                throw new ObjectDisposedException(null);
        }

        protected abstract void HashCore(byte[] array, int ibStart, int cbSize);
        protected abstract byte[] HashFinal();
        public abstract void Initialize();
    }

Before saving the generated document, you should set some properties like the following:

            pdf.SecuritySettings.DocumentSecurityLevel = PdfSharpCore.Pdf.Security.PdfDocumentSecurityLevel.Encrypted128Bit;
            pdf.SecuritySettings.OwnerPassword = "abc";
            pdf.SecurityHandler.PrepareEncryption();
            pdf.SecurityHandler.EncryptDocument();
highfield commented 6 years ago

Well, not enough...

https://github.com/empira/PDFsharp/issues/19

For me is working by commenting those lines out:

            //if (dict.Stream != null)
            //{
            //    byte[] bytes = dict.Stream.Value;
            //    if (bytes.Length != 0)
            //    {
            //        PrepareKey();
            //        EncryptRC4(bytes);
            //        dict.Stream.Value = bytes;
            //    }
            //}

https://github.com/groege/PdfSharpCore/blob/a429b03519d4290096118642ebf5e536652aba74/PdfSharpCore/Pdf.Security/PdfStandardSecurityHandler.cs#L168

groege commented 6 years ago

I'm not familiar with that part of the code - feel free to send a pull request if you have a good solution.