EldoS | Feel safer!

Software components for data protection, secure storage and transfer

Locally emulate distributed signing of a pdf document

Also by EldoS: Rethync
The cross-platform framework that simplifies synchronizing data between mobile and desktop applications and servers and cloud storages
#30972
Posted: 10/13/2014 09:10:01
by Anders Tornqvist (Standard support level)
Joined: 03/03/2014
Posts: 8

Hi,

We're currently working on having a PDF signed by a third-party. As a result, we receive a PKCS#7 SignedData byte-array which we now need to insert into the PDF.

I have studied the very similar question found here (https://www.eldos.com/forum/read.php?PAGEN_1=2&TID=4536), and I have tried to process the async state object directly, as demonstrated by Bogatskyy (https://www.eldos.com/forum/read.php?PAGEN_1=2&FID=7&TID=4536#message25378).

However, when I try to call "CompleteAsyncOperation", it responds with the exception "Signing failed (error 8224 )", which seams to mean "Wrong Distributed Cryptography asynchronous operation state".

What is the cause of this?


This is the code (note that this is the same code as posted by Bogatskyy, but with the OperationResult populated by the preexisting PKCS#7 SignedData byte-array):


Code
        public void EmulateDistribSigningPDF(string fileName, byte[] signedDataPkcs7)
        {
            SBPDF.Unit.Initialize();
            SBPDFSecurity.Unit.Initialize();

            string TempPath = "sample-presigned.pdf";
            // preparing a temporary file copy
            File.Copy(fileName, TempPath, true);

            TElDCAsyncState state;
            TElPDFDocument doc;
            TElPDFPublicKeySecurityHandler handler;
            using (FileStream input = new FileStream(TempPath, FileMode.Open, FileAccess.ReadWrite))
            {
                doc = new TElPDFDocument();
                handler = new TElPDFPublicKeySecurityHandler();
                try
                {
                    doc.Open(input);

                    handler.SignatureType = TSBPDFPublicKeySignatureType.pstPKCS7SHA1;
                    handler.HashAlgorithm = SBConstants.__Global.SB_ALGORITHM_DGST_SHA1;
                    handler.CustomName = "Adobe.PPKMS";

                    TElPDFSignature signature = doc.get_Signatures(doc.AddSignature());
                    signature.SignatureType = SBPDF.__Global.stDocument;
                    signature.Invisible = true;
                    signature.Handler = handler;
                    signature.SigningTime = DateTime.UtcNow;

                    state = doc.InitiateAsyncOperation();
                    handler.Dispose();
                }
                finally
                {
                    handler.Dispose();
                    doc.Dispose();
                }
            }

            // saving the obtained state to file for debug purposes
            using (FileStream output = new FileStream("state-01.xml", FileMode.Create, FileAccess.ReadWrite))
            {
                state.SaveToStream(output, SBDCXMLEnc.Unit.DCXMLEncoding());
            }

            state.Dispose();


            //////////////////////////////////////////
            // processing the state object directly //
            //////////////////////////////////////////
            state = new TElDCAsyncState();
            using (FileStream input = new FileStream("state-01.xml", FileMode.Open, FileAccess.Read))
            {
                state.LoadFromStream(input, SBDCXMLEnc.__Global.DCXMLEncoding());
            }

            TElDCAsyncState respState = new TElDCAsyncState();
            for (int i = 0; i < state.Messages.Count; i++)
            {
                TElDCBaseMessage msg = state.Messages.get_Messages(i);
                if (msg is TElDCOperationRequestMessage)
                {
                    TElDCOperationResponseMessage respMsg = new TElDCOperationResponseMessage();
                    respMsg.Operation = TSBDCOperation.dcRawSign;
                    byte[] digest = ((TElDCOperationRequestMessage)msg).Source;
                    byte[] hashAlgorithm = ((TElDCOperationRequestMessage)msg).HashAlgorithm;

                    // As we already have the signature, we just pass it along
                    respMsg.OperationResult = signedDataPkcs7;

                    respState.Messages.Add(respMsg, false);
                }
                else if (msg is TElDCBaseMessage)
                {
                    // base messages are mirrored
                    TElDCBaseMessage msgTmp = new TElDCBaseMessage();
                    msgTmp.Assign(msg);
                    respState.Messages.Add(msgTmp, false);
                }
            }

            using (FileStream output = new FileStream("state-02.xml", FileMode.Create, FileAccess.ReadWrite))
            {
                respState.SaveToStream(output, SBDCXMLEnc.Unit.DCXMLEncoding());
            }


            //////////////////////////////
            // finalizing the signature //
            //////////////////////////////
            state = new TElDCAsyncState();
            using (FileStream input = new FileStream("state-02.xml", FileMode.Open, FileAccess.Read))
            {
                state.LoadFromStream(input, SBDCXMLEnc.__Global.DCXMLEncoding());
            }

            doc = new TElPDFDocument();
            handler = new TElPDFPublicKeySecurityHandler();
            try
            {
                handler.SignatureType = TSBPDFPublicKeySignatureType.pstPKCS7SHA1;

                using (FileStream fs = new FileStream(TempPath, FileMode.Open, FileAccess.ReadWrite))
                {
                    doc.CompleteAsyncOperation(fs, state, handler);
                }
            }
            finally
            {
                handler.Dispose();
                doc.Dispose();
            }

            state.Dispose();
            File.Copy(TempPath, "sample-signed.pdf", true);
        }



Thanks.
#30981
Posted: 10/14/2014 04:36:18
by Dmytro Bogatskyy (EldoS Corp.)

Thank you for contacting us.

The code above (that copied from the user code) is slightly incorrect, you can't pre-generate signedDataPkcs7 value prior calling InitiateAsyncOperation, you should extract the digest value from async state and sign it with a third-party, then set OperationResult property. Please, see my answer:
https://www.eldos.com/forum/read.php?P...ssage25378 (in the last code block there is a placeholder for your signing code).

P.S. There is a small typo in the above code, the "handler.Dispose();" in the first try..finally block called twice, first call should be removed.
#30982
Posted: 10/14/2014 05:55:15
by Anders Tornqvist (Standard support level)
Joined: 03/03/2014
Posts: 8

Hi,

Thank you for your reply.


Yes, I read the old post-thread again and saw that the digest (extracted from the async state) was to be signed. I have actually tried this as well, but with the same exception as before. I have tried by both populating the OperationResult by a PKCS1 v1.5 signature, and also by a full CMS SignedData structure.

However, I was just about to rephrase my question, and split it up into two questions to clarify:

1) Today we create a CMS SignedAttr field (containing the digest extracted from the async state) which we then send to the third-party for signing (they accept nothing but a SignedAttr). After we receive the signature we build the remaining CMS SignedData structure (with a signerInfo which includes the SignedAttr and its signature etc.). This is done, today, using BouncyCastle. Is it possible to build the CMS SignedData using BlackBox?

2) So after successfully creating the full CMS SignedData structure, we must insert this into the prepared PDF. Is this possible using BlackBox? Also, is it possible to sign the same PDF again, thus resulting in two (or more) signatures within the PDF?


Best regards.
#30984
Posted: 10/14/2014 07:29:50
by Ken Ivanov (EldoS Corp.)

Hi Anders,

Thank you for the details.

The CompleteAsyncOperation() actually expects the returned state to carry a PKCS#1 signature over the data, not a PKCS#7 one. If you are passing back a PKCS#7 blob, you are likely to get the wrong state exception.

So, do I understand you correctly that what you are trying to implement is, basically,

1) get the hash of the document (which is supposed to go to the message-digest attribute of the SignedAttr structure) from SecureBlackbox, then

2) compose a SignedAttr structure,

3) send it for signing to a third-party service, and then

4) use the returned signature to construct the SignedData structure?

If that is the case, and you are OK with doing it synchronously (with the same TElPDFDocument instance), then DC is probably not the best way of achieving it.

Instead, a good idea would be to implement your own PDF security handler which will be receiving the hash from the associated TElPDFDocument object, composing the signature object, and passing the final SignedData signature straight back to the document. Your security handler will inherit from the out-of-the-box TElPDFPublicKeySecurityHandler class, and will need to override its SignHash() method.

Manipulation with PKCS#7 structures is achievable with low-level PKCS#7 classes defined in the SBPKCS7 module. They are not documented due to their internal nature, yet they are fairly straightforward to use.

Quote
2) So after successfully creating the full CMS SignedData structure, we must insert this into the prepared PDF. Is this possible using BlackBox?

By overriding the security handler component you will delegate this task to SecureBlackbox internals. You will only need to return your PKCS#7 structure from the SignHash() method of your handler object, and SecureBlackbox will put it to the right place of the document for you.

Quote
Also, is it possible to sign the same PDF again, thus resulting in two (or more) signatures within the PDF?

Yes, you can manage this in a standard way.

Ken
#30986
Posted: 10/14/2014 09:59:38
by Anders Tornqvist (Standard support level)
Joined: 03/03/2014
Posts: 8

Hi,

Thank you for quick response, and yes, your implementation-description is correct.

Implementing a custom PDF security handler seems close to what we need. However, we have the following issues:

1) We have no knowledge of the certificate being used to signing the SignedAttr beforehand (although maybe we could predict the Serialnumber within the subject commonName), and we only receive the public certificate at the SignedAttr signature response. TElPDFDocument, on Close, seems to crave the private signing part, which we don't have.

2) The signature response could take days, so we cannot keep the same TElPDFDocument instance. The best thing for us would be if we could work only with the PDF document (which is the only data we can store), preparing and maybe store the prepared version, or redo the preparation of the PDF document once the signature response comes?

Would this be possible to solve using BlackBox?


Best regards.
#30991
Posted: 10/15/2014 05:14:18
by Ken Ivanov (EldoS Corp.)

Hi Anders,

Right, thank you for the additional details.

The absence of the certificate on the pre-signing stage is no problem, however, the delay with the response is. The solution I've suggested above works in synchronous way and requires the state (represented by TElPDFDocument and related objects) to be preserved within the operating environment.

However, there is a different option which might work for you. Currently we are working on extending DC capabilities with different signing methods within the course of SecureBlackbox 13 development process. Upon release of SecureBlackbox 13, the distributed crypto components will be able to delegate signing to PKCS#7 engines in addition to currently supported PKCS#1 ones. This will match your circumstances perfectly, as you will be getting a hash of the data in form of asynchronous state, and will be expected to pass a complete PKCS#7 signature back to the components later (generally, in a different state and environment).

However, this functionality is not ready yet, and the earliest I believe it could be available for evaluation is the beginning of November (with a beta status). So this option will only work if you are happy to wait for that functionality to be released.

Ken
#30994
Posted: 10/15/2014 07:57:24
by Anders Tornqvist (Standard support level)
Joined: 03/03/2014
Posts: 8

Hi Ken,

Thanks for the response.

Would it be possible to store and read the state as seen in the async example? The SecureBlackBox 13 solution sounds good, but I'm afraid we don't have time on our side, and even if we did we won't be able to update BlackBox to any other (stable) version in the future - once we implemented the solution.

Is there any other way this could be achieved using BlackBox?


Best regards.
#31017
Posted: 10/16/2014 03:20:14
by Ken Ivanov (EldoS Corp.)

Hi Anders,

I gave it a little thought, and I believe your problem still can be solved by utilising a custom security handler approach I described in message #30984 above. Yet, as you can't do the signing in one pass due to asynchronous nature of your signing environment, the process will be slightly more sophisticated.

Basically, the signing will be done in two steps. On the first step, you will use the components in the standard way to pre-form the signed document and calculate the hash for you. No actual signing will take place on this stage. On the second step, which will be performed upon receipt of the signature from the signing engine, you will use the components again to pre-form the signed document again and put the signature into the document.

Your custom handler, this way, will operate in two different modes, 'pre-signing' and 'post-signing'. In the pre-signing mode the SignHash() method will only pass the obtained hash to your code, which will then deal with it accordingly. No (or dummy) signature will be returned from SignHash() in this mode, and the resulting 'signed' document may be discarded. In the post-signing mode, the handler will take a signature blob from your code and pass it back to TElPDFDocument from SignHash() method.

As hash calculation is a well-defined process which does not involve any randomly chosen parameters (except signing time, which you should take care of), you will always get the same pre-formed document and its hash when signing the same document with the same set of parameters. This means that you can launch the signing procedure twice, using the first launch to obtain the hash and the second launch to place the actual signature to the document.

Ken
#31033
Posted: 10/17/2014 04:11:42
by Anders Tornqvist (Standard support level)
Joined: 03/03/2014
Posts: 8

Hi Ken,

Thanks for the reply.

I have experimented a bit with the process you described. So I created the basic signature procedure as described in SBB PDF signing "How To", but now with my own security handler overriding the SignHash method. To see if things work I stored the SignedData result from the SignHash base method to file on the first run (the first step, or 'pre-signing', as you described it). On the second run I had the SignHash read and return the stored SignedData from file. This seams to work to some extent, but I have the following issues:

1) It appears (as stated in my previous post, #30986) I need to assign a storage with certificate(s) to the security handler, otherwise TElPDFDocument will throw a "No signing certificate found" exception on close/save.

2) If I do assign a storage and add a certificate, I must do so with a certificate which contains the private-key. Otherwise TElPDFDocument will throw the same exception as in #1 on close/save, before even calling SignHash.

3) When I assign a storage and include a certificate (with the private key to it) everything works fine (and Adobe can successfully validate the signature), but only if the included certificate is the same which were used to sign the SignedAttr (now returned within the SignedData from file). If I assign any other dummy certificate the signed PDF will still be created, and the certificate seen in the PDF's Signature Panel will be the one which signed the SigendAttr (not the dummy certificate), but Adobe will alert that "At least one signature is invalid".

So the question now remains if it is possible to solve these remaining issues?


Best regards.
#31036
Posted: 10/17/2014 10:29:47
by Ken Ivanov (EldoS Corp.)

Hi Anders,

You'll have to implement one more trick to suppress the 'No signing certificate' exception. The exception is thrown from within the inherited TElPDFPublicKeySecurityHandler.GetEstimatedSignatureSize() method, which is used to estimate the size of the signature blob. What you need to do is override this method in your customized security handler and call the parent method with AsyncMode set to true. This will help to get rid of the exception.

Regarding the third point, could you please send us a sample signed document illustrating the problem? In your circumstances the certificate assigned to the CertStorage should not be involved in computations at all, so there should be a different reason for signature invalidity.

You are welcome to post the sample document to the Helpdesk (as the forum won't accept any large or non-textual files). I've created a ticket for you.

Ken
Also by EldoS: CallbackProcess
A component to control process creation and termination in Windows and .NET applications.

Reply

Statistics

Topic viewed 1370 times

Number of guests: 1, registered members: 0, in total hidden: 0




|

Back to top

As of July 15, 2016 EldoS Corporation will operate as a division of /n software inc. For more information, please read the announcement.

Got it!