We are programmatically creating PDF using our in house lib (C++) by adding all the required objects so that PDF readers can render them properly. Currently we are enhancing the lib to support digital signatures in PDF. Our users will use USB token or Windows certificates to sign the PDF. On studying raw PDF file with digital signature, we were able to make sense of all the objects except for the contents of Sig type object.
18 0 obj
<<
/Type /Sig
/Filter /Adobe.PPKLite --> signature handler for authenticating the fields contents
/SubFilter /adbe.pkcs7.sha1 --> submethod of the handler
/Contents <....> --> signature token
/ByteRange [ 0 101241 105931 7981
] --> byte range for digest calc
/M (D:20210505094125+05'30') --> timestamp
/Reason ()
/Location ()
/ContactInfo ()
>>
endobj
We have referred https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSigDC/Acrobat_DigitalSignatures_in_PDF.pdf to understand what all constitutes the signature token. We need direction on how to programmatically create the signature token for PDF using windows APIs. Currently we are not looking at 3rd party lib solutions. Thanks in advance.
Update
We tried the following:
- Updated our in-house PDF lib to support incremental updates so that digital signing related objects can be added. We added something like this apart from the obj# 18 mentioned above:
16 0 obj --> new Acroform obj
<<
/Fields [ 17 0 R ]
/SigFlags 3
>>
endobj
2 0 obj --> Updating root to add AcroForm
<<
/Type /Catalog
/Pages 3 0 R
/AcroForm 16 0 R
>>
endobj
17 0 obj --> new obj for signature field
<<
/T (SignatureField1)
/Type /Annot
/Subtype /Widget
/FT /Sig
/F 4
/Rect [ 270 159 503 201 ] --> field position. this will have image of sign
/P 5 0 R
/V 18 0 R
/AP <<
/N 19 0 R
>>
>>
endobj
5 0 obj --> updating existing page obj with Annots
<<
/Type /Page
/Parent 3 0 R
/MediaBox [ 0 0 595 841 ]
/Resources 4 0 R
/Contents 6 0 R
/Annots [ 17 0 R ]
>>
endobj
18 0 obj
<<
/Type /Sig
/Filter /Adobe.PPKLite
/SubFilter /adbe.pkcs7.sha1 --> we tried with adbe.pkcs7.detached as well
/Contents <> --> updated contents using windows APIs
/ByteRange [ 0 100381 102645 7322
] --> updated ByteRange with right offsets and lengths
/M (D:20210610125837+05'30') --> sign verified time
/Reason ()
/Location ()
/ContactInfo ()
>>
endobj
19 0 obj --> new obj
<<
/Length 7
/BBox [ 0 0 233 42 ]
/Type /XObject
/Subtype /Form
/Resources <<
/XObject <<
/FRM 20 0 R
>>
>>
>>
stream
/FRM Do
endstream
endobj
20 0 obj --> new obj for image manipulation
<<
/Length 29
/Type /XObject
/Subtype /Form
/Resources <<
/XObject <<
/Im1 21 0 R
>>
>>
/BBox [ 0 0 233 42 ]
>>
stream
q 233 0 0 42 0 0 cm /Im1 Do Q
endstream
endobj
21 0 obj --> image obj which contains sign info. Generated by us
<<
/Length 6166
/Type /XObject
/Subtype /Image
/Width 372
/Height 82
/ColorSpace /DeviceRGB
/BitsPerComponent 8
/Filter /DCTDecode
>>
stream
---------------------------------> image stream
endstream
endobj
xref --> updated xref
0 1
0000000000 65535 f
2 1
0000099954 00000 n
5 1
0000100172 00000 n
16 6
0000099901 00000 n
0000100020 00000 n
0000100297 00000 n
0000102944 00000 n
0000103096 00000 n
0000103271 00000 n
trailer --> updated trailer
<<
/Root 2 0 R
/Info 1 0 R
/Size 22
/ID [ <982AAACB948CE1AD9FDD976D177BF316> <982AAACB948CE1AD9FDD976D177BF316> ]
--> ID generated via windows API
/Prev 99491
>>
startxref
109605
%%EOF
- For contents data, we used the below API:
bool SignMessageBySubjectName (BytePtr pMessage, ULong pMessageSize, StrPtr pSubjectName, CRYPT_DATA_BLOB * pSignBlob)
{
HCERTSTORE store_handle = NULL;
PCCERT_CONTEXT cert_context = NULL;
BYTE * signed_blob = NULL;
ULong signed_blob_size;
ULong message_size;
CRYPT_SIGN_MESSAGE_PARA signature_params;
BYTE * message;
pSignBlob->cbData = 0;
pSignBlob->pbData = NULL;
message = (BYTE *) pMessage;
message_size = (pMessageSize + 1) * sizeof(Char); //Size in bytes
const BYTE * message_array[] = {message};
DWORD message_array_size[1];
message_array_size[0] = message_size;
store_handle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL,
CERT_SYSTEM_STORE_CURRENT_USER, L"MY");
cert_context = CertFindCertificateInStore( store_handle, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0,
CERT_FIND_SUBJECT_STR, pSubjectName, NULL);
signature_params.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
signature_params.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
signature_params.pSigningCert = cert_context;
signature_params.HashAlgorithm.pszObjId = szOID_RSA_SHA1RSA;
signature_params.HashAlgorithm.Parameters.cbData = NULL;
signature_params.cMsgCert = 1;
signature_params.rgpMsgCert = &cert_context;
signature_params.cAuthAttr = 0;
signature_params.dwInnerContentType = 0;
signature_params.cMsgCrl = 0;
signature_params.cUnauthAttr = 0;
signature_params.dwFlags = 0;
signature_params.pvHashAuxInfo = NULL;
signature_params.rgAuthAttr = NULL;
//Get size of signed message
CryptSignMessage(&signature_params, TRUE, 1, message_array, message_array_size,NULL, &signed_blob_size);
signed_blob = (BYTE *) Malloc(signed_blob_size);
CryptSignMessage(&signature_params, TRUE, 1, message_array, message_array_size, signed_blob, &signed_blob_size);
pSignBlob->cbData = signed_blob_size;
pSignBlob->pbData = signed_blob;
CertFreeCertificateContext(cert_context);
CertCloseStore(store_handle, CERT_CLOSE_STORE_FORCE_FLAG);
return true;
}
While using CryptSignMessage() with detached parameter as TRUE, we get a around 850 length sign token which we convert to hex and add in the contents part. It'll approximately be around 1700 chars.
In case of the image used in the Field newly added, we generated our own image and added it as a PDF obj.
For the ID in trailer part, we generated the same using API from Bcrypt.lib (BCryptGenRandom()), converted its output to hex and updated the ID part.
Listing out the steps we did:
We generated 2 buffers. Both buffers are identical with respect to all the PDF objects required, the ID generated from BCryptGenRandom() and ByteRange array updated with actual values. buffer1 has contents data as 0s for a definite length acting as a placeholder. buffer2 has empty contents data (/Contents <>)
buffer2 will be passed onto CryptSignMessage() to generate the sign token. This will be converted to hex.
The hex sign token will be added to contents part of buffer1 replacing the 0s based on its length.
buffer1 will be written to a PDF file.
When we did all these, and opened the PDF in readers, we got errors like
- Signature is invalid
- Document has been corrupted or altered since the signature was applied.
Error from a PDF Reader:
Detailed Error:
But with these errors too, the reader was able to identify the user, certificate, hash algorithm and signature algorithm used.
We think we need to somehow add timestamp data as part of the sign token so as to avoid this error. Or something else we would have missed.
PFA sample PDF here:https://drive.google.com/file/d/1Udog4AmGoq2ls3Tu3Wq5s2xU9LxaI3fH/view?usp=sharing
Kindly help us solve this issue. Thanks in advance.