0
votes

I have partially read itextpdf's paper about digital signatures and from it, I started developing my code:

public class ServerSignature implements ExternalSignature {
    public String getHashAlgorithm() {
      return DigestAlgorithms.SHA256;
    }
    public String getEncryptionAlgorithm() {
      return "RSA";
    }
    public byte[] sign(byte[] message) throws GeneralSecurityException {
      byte[] s = Base64.getDecoder().decode(SPMService.this.signature);
      return s;
    }
  }

public void sign(Certificate[] chain, CryptoStandard subfilter, String reason,
      String location) throws GeneralSecurityException, DocumentException, IOException {
    // Creating the reader and the stamper
    System.out.println("SRC: " + this.SRC);
    PdfReader reader = new PdfReader(this.SRC);
    FileOutputStream os = new FileOutputStream(this.DEST);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
    // Creating the appearance
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setReason(reason);
    appearance.setLocation(location);

    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");

    // Creating the signature
    ExternalDigest digest = new BouncyCastleDigest();
    ExternalSignature signature = new ServerSignature();
    MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
    this.verifySignatures(this.DEST);
  } 

    private void verifySignature(AcroFields fields, String name) throws GeneralSecurityException, IOException {
    // Returns true
    System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
    // Returns the right revisions, e.g. 1 of 1
    System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
    PdfPKCS7 pkcs7 = fields.verifySignature(name);
    // Returns SHA256
    System.out.println("Digest algorithm: " + pkcs7.getHashAlgorithm());
    //Returns RSA
    System.out.println("Encryption algorithm: " + pkcs7.getEncryptionAlgorithm());
    // Return /adbe.pkcs7.detached
    System.out.println("Filter subtype: " + pkcs7.getFilterSubtype());
    // Get the signing certificate to find out the name of the signer.
    X509Certificate cert = (X509Certificate) pkcs7.getSigningCertificate();
    System.out.println("Name of the signer: " + CertificateInfo.getSubjectFields(cert).getField("CN"));
    if (pkcs7.getSignName() != null) {
      System.out.println("Alternative name of the signer: " + pkcs7.getSignName());
    }
    // The following log is returning false always. Why?
    System.out.println("Integrity check OK? " + pkcs7.verify());
  }
  private void verifySignatures(String src) throws IOException, GeneralSecurityException {
    Security.addProvider(new BouncyCastleProvider());
    PdfReader reader = new PdfReader(src);
    AcroFields fields = reader.getAcroFields();
    ArrayList<String> names = fields.getSignatureNames();
    for (String name : names) {
      System.out.println("===== " + name + " =====");
      verifySignature(fields, name);
    }
    System.out.println();
  }
  public void start() throws GeneralSecurityException, DocumentException, IOException {
    CertificateFactory factory = CertificateFactory.getInstance("X.509");
    // certificate I received in the Controller
    InputStream targetStream = new ByteArrayInputStream(this.certificate.getBytes());
    Certificate[] chain = new Certificate[1];
    chain[0] = factory.generateCertificate(targetStream);
    this.sign(chain, CryptoStandard.CMS, "Reason", "Location");
  }

With this code, when I open a signed document with Adobe Acrobat Reader, the signature is visible and is also embedded in the document. The problem is that it's with a red mark stating that the "Document has been altered or corrupted since it was signed".

Can someone explain to me what's missing or if my logic is wrong?

2
Please share an example PDF signed by your code for analysis.mkl
There appears to be an error in ServerSignature: In the embedded signature container the signature bytes sign the wrong hash value (not the hash of the signed attributes but some other value).mkl
can you give me an example, please?pmpc
"can you give me an example" - an example of what? I just wrote an answer with more details of the analysis of your example PDF.mkl

2 Answers

3
votes

The problem is not in the code you show. Thus, it's either an issue of the ServerSignature you use or of the server side code addressed by it.

In Detail

Analyzing the example PDF you shared one can see:

  • The SHA256 digest of the signed byte ranges in the PDF is

    37B415ACC3E5A146073E8C95D5DEE7E93190CF082210A9CE53AD78F7C5D58002
    

    and indeed, the embedded signature container SignerInfo contains the signed attribute

    . . . . . . SEQUENCE {
    . . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
    . . . . . . . . (PKCS #9)
    . . . . . . . SET {
    . . . . . . . . OCTET STRING    
    . . . . . . . . . 37 B4 15 AC C3 E5 A1 46    7......F
    . . . . . . . . . 07 3E 8C 95 D5 DE E7 E9    .>......
    . . . . . . . . . 31 90 CF 08 22 10 A9 CE    1..."...
    . . . . . . . . . 53 AD 78 F7 C5 D5 80 02                            
    . . . . . . . . }
    . . . . . . . }
    

    Thus, the digest calculation of the signed bytes ranges, its use in the signature container creation, and the embedding of the signature container worked correctly.

  • The signature bytes can successfully be decrypted to a DigestInfo object with PKCS#1 1.5 padding. Thus, the private key used for encryption matches the public key in the alleged signer certificate.

  • The SHA256 digest of the signed attributes in your signature container is

    0083D5FA72C3D81EA038BF62D4AFFC949F23E120DA59E59C5CB8EFA5214BA975
    

    but the decrypted signature bytes (PKCS#1 1.5 padding removed) are

    3031300D0609608648016503040201050004208DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
    

    so the actually signed SHA256 digest is

    8DECC8571946D4CD70A024949E033A2A2A54377FE9F1C1B944C20F9EE11A9E51
    

    Obviously there is a mismatch. Thus, apparently the signed attribute bytes have not been properly processed in the ExternalSignature implementation ServerSignature you use or in the backend it addresses.

Unfortunately you neither show the ServerSignature implementation code you use nor any code on the server in question. Thus, the issue as is cannot be analyzed any further; it's merely clear that the iText signing code you show is working properly.

Your Code

Meanwhile you shared the ServerSignature implementation code you use:

public class ServerSignature implements ExternalSignature {
    public String getHashAlgorithm() {
        return DigestAlgorithms.SHA256;
    }
    public String getEncryptionAlgorithm() {
        return "RSA";
    }
    public byte[] sign(byte[] message) throws GeneralSecurityException {
        byte[] s = Base64.getDecoder().decode(SPMService.this.signature);
        return s;
    }
}

Here one sees that you completely ignore the byte[] message but return some SPMService.this.signature. As you ignore the message to sign, the resulting PDF signature statistically must be invalid.

So change your code here to make your ServerSignature.sign method return signature bytes which sign the given byte[] message.

0
votes

Three possibilities spring to mind:

  1. You used the wrong region of the document to verify against. That's a really easy mistake I've made several times, though I've never tried working against PDF, so I can't help there. Obviously, if you're off by so much as a byte, you'll get a mismatch. And, also probably obviously, you don't calculate against the entire file; at the very least you must exclude the signature value, since that wasn't there when the original signature was calculated. Quite possibly some other fields are omitted too.

  2. You used the wrong algorithm. I don't know the PDF stuff, so I can't comment, but how did you choose the algorithm you used? Are you confident it's the right one?

  3. The document really has been tampered with.