0

I am trying to sign a pdf using a remote web service which returns PKCS#7 signature. I sent calculated file hash which need to signing, and service return Base64Encoded PKCS#7 certificate.

I read some cases like me (link1 link2). After that this implementation should meet my flow.

Signing Method

public static void sign(string unsignedPdf, string signedPdf)
{
    PdfReader reader = new PdfReader(unsignedPdf);
    FileStream os = new FileStream(signedPdf, FileMode.Create);
    PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
    // Creating the appearance
    PdfSignatureAppearance appearance = stamper.SignatureAppearance;
    appearance.Reason = "For a reason surely";
    appearance.Location = "Positively somewhere";
    appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
    IExternalSignatureContainer externalSignatureContainer = new ExternalServiceContainerSigner();
    // Creating the signature
    MakeSignature.SignExternalContainer(appearance, externalSignatureContainer, 8192);
}

ExternalSignatureContainer implementation

class ExternalServiceContainerSigner : IExternalSignatureContainer
{
    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
        signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
    }

    public byte[] Sign(Stream data)
    {
        String hashAlgorithm = "SHA256";
        byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);

        var transId = Program.GetFixedLengthStrinng(8);
        var transDate = XmlConvert.ToString(DateTime.Now);

        string pkcsBase64 = MobileSignClient.getSignatureAsync(null, null, transId, transDate, calculatedHash: Convert.ToBase64String(hash)).Result;

        var retVar = Convert.FromBase64String(pkcsBase64);

        return retVar;
        
    }
}

Service response for "JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg=" hash: gist

Original File: https://drive.google.com/file/d/19YgUTy9rL8tLZKU_EdRmkBJHvor1zmTw/view?usp=sharing

Signed File: https://drive.google.com/file/d/1vQnBKdpOlOnQtDyveC0017TXh90ZVHfR/view?usp=sharing

After signing, signature looks invalid.

enter image description here

What I am missing?

shihabudheenk
  • 593
  • 5
  • 18
Dreamcatcher
  • 798
  • 13
  • 31

1 Answers1

0

First the good news: The base64 encoded hash you mention, JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg= is the correct SHA256 hash value of the signed byte ranges of the PDF. Thus, the error must be in these few lines that request a CMS signature for that hash value:

var transId = Program.GetFixedLengthStrinng(8);
var transDate = XmlConvert.ToString(DateTime.Now);

string pkcsBase64 = MobileSignClient.getSignatureAsync(null, null, transId, transDate, calculatedHash: Convert.ToBase64String(hash)).Result;

var retVar = Convert.FromBase64String(pkcsBase64);

Then the bad news: The CMS signature container returned by these lines is wrong in many ways:

  • The signature container contains encapsulated content:

     SEQUENCE (2 elem)
       OBJECT IDENTIFIER 1.2.840.113549.1.7.1 data (PKCS #7)
       [0] (1 elem)
         OCTET STRING (44 byte) JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg=
    

    This is incorrect, No data shall be encapsulated for embedded PDF signatures of type adbe.pkcs7.detached (ISO 32000-2:2020 section 12.8.3.3).

    Possible solution: The signing API should offer a flag to request signatures without encapsulating any signed content.

  • The messageDigest signed attribute contains the SHA1 hash of the encapsulated content.

    SEQUENCE (2 elem)
      OBJECT IDENTIFIER 1.2.840.113549.1.9.4 messageDigest (PKCS #9)
      SET (1 elem)
        OCTET STRING (20 byte) 03AC720F1BE912035E483BF07486319D07631B35
    

    This is understandable given that there wrongly is encapsulated content, see previous item. What should be here, though, is the hash value you provided, not another hash of it.

    Possible solution: In addition to the flag above, the signing API should offer a flag to indicate that the value you send is not the plain data to sign but already the hash thereof.

    By the way, it is extremely weird that SHA1 is used here as the digest algorithm mentioned in the signature is SHA256...

  • The digest in the encrypted signature value is your provided hash value.

    Given the presence of encapsulated data and signed attributes, this is completely wrong. It should have been the digest of the combined encapsulated content and signed attributes or at least the digest of the signed attributes but not the digest value you provided.

Thus, the next thing you should do is study the parameters of your MobileSignClient (using the documentation and/or trial&error) and find a signing call that returns a CMS signature container with the required contents.

mkl
  • 90,588
  • 15
  • 125
  • 265