如何在 C 或 C++ 中为 PDF 中的数字签名创建 Sig 类型对象的内容值?

How to create in C or C++ the contents value of Sig type object for digital signature in PDF?

我们正在使用我们的内部库 (C++) 通过添加所有必需的对象以编程方式创建 PDF,以便 PDF readers 可以正确呈现它们。 目前我们正在增强 lib 以支持 PDF 中的数字签名。我们的用户将使用 USB 令牌或 Windows 证书来签署 PDF。 在研究带有数字签名的原始 PDF 文件时,我们能够理解除 Sig 类型对象内容之外的所有对象。

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

我们提到了 https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSigDC/Acrobat_DigitalSignatures_in_PDF.pdf 了解所有构成签名令牌的内容。 我们需要有关如何使用 windows APIs 以编程方式为 PDF 创建签名令牌的指导。目前我们不考虑第 3 方库解决方案。 提前致谢。

更新

我们尝试了以下方法:

  1. 更新了我们的内部 PDF 库以支持增量更新,以便可以添加与数字签名相关的对象。除了上面提到的 obj# 18 之外,我们还添加了类似这样的内容:
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

  1. 对于内容数据,我们使用了以下 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;
}

当使用 CryptSignMessage() 并将分离参数设置为 TRUE 时,我们得到一个大约 850 长的符号令牌,我们将其转换为十六进制并添加到内容部分。大约有 1700 个字符。

  1. 对于新添加的字段中使用的图像,我们生成了自己的图像并将其添加为PDF对象。

  2. 对于预告片部分的 ID,我们使用 API 从 Bcrypt.lib (BCryptGenRandom()) 生成相同的 ID,将其输出转换为十六进制并更新 ID 部分。

  3. 列出我们执行的步骤:

当我们完成所有这些操作并在 reader 秒内打开 PDF 时,出现了类似

的错误

PDF 错误 Reader:

详细错误:

但也有这些错误,reader 能够识别用户、证书、哈希算法和使用的签名算法。

我们认为我们需要以某种方式添加时间戳数据作为签名令牌的一部分,以避免此错误。或者我们会错过的其他东西。

PFA 样本 PDF 在这里:https://drive.google.com/file/d/1Udog4AmGoq2ls3Tu3Wq5s2xU9LxaI3fH/view?usp=sharing

请帮助我们解决这个问题。提前致谢。

好的,签名容器嵌入正确

但签名容器本身存在问题:

  • 您在 SignedData.digestAlgorithms 集合和 SignerInfo.digestAlgorithm 值中都使用了 SHA1withRSA 的 OID,但这是一个完整的签名算法,而不是单纯的摘要算法 SHA1

  • 然后签名字节的 SHA1 散列是 BB78A402F7A537A34D6892B83881266501A691A8 但你签名的散列是 90E28B8A0D8E48691DAFE2BA10A4761FFFDCCD3D。这可能是因为您散列 buffer2 和

    buffer2 has empty contents data (/Contents <>)

    十六进制字符串分隔符“<”和“>”也属于内容值,因此也必须在缓冲区 2 中删除。

另外,你的签名很弱:

  • 它使用SHA1作为散列算法。同时,SHA1 被认为是用于文档签名的哈希算法太弱。
  • 它不使用签名属性,既不使用ESS签名证书也不使用算法标识符保护属性。许多验证策略需要此类特殊属性。

我们使用了一组不同的 API 来完成这项工作。 在此处粘贴代码:

bool SignatureHandler::SignMessageTest (BytePtr pMessage, ULong pMessageSize, StrPtr pSubjectName, CRYPT_DATA_BLOB * pSignBlob, LPSTR pOid, DWORD pFlag, DWORD pType)
{
        HCERTSTORE              store_handle  = NULL;
        PCCERT_CONTEXT          cert_context  = NULL;
        BYTE *                  signed_blob   = NULL;
        ULong                   signed_blob_size = 0;
        CRYPT_SIGN_MESSAGE_PARA signature_params;
        BYTE *                  message;
        BOOL                    rc;

        pSignBlob->cbData = 0;
        pSignBlob->pbData = NULL;

    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);

        HCRYPTPROV_OR_NCRYPT_KEY_HANDLE a         = 0;
        DWORD                           ks        = 0;
        BOOL                            bfr       = false;

        HCRYPTPROV_OR_NCRYPT_KEY_HANDLE PrivateKeys;
        CERT_BLOB                       CertsIncluded;
        CMSG_SIGNER_ENCODE_INFO         Signers;

        HCRYPTMSG                       hMsg;

    rc = CryptAcquireCertificatePrivateKey (cert_context, 0, 0, &a, &ks, &bfr);

        CMSG_SIGNER_ENCODE_INFO SignerEncodeInfo = {0};

    SignerEncodeInfo.cbSize = sizeof (CMSG_SIGNER_ENCODE_INFO);

    if (a)
        SignerEncodeInfo.hCryptProv = a;

    if (bfr)
        PrivateKeys = a;

    CERT_BLOB SignerCertBlob;
    
    SignerCertBlob.cbData = cert_context->cbCertEncoded;
    SignerCertBlob.pbData = cert_context->pbCertEncoded;
    CertsIncluded         = SignerCertBlob;

                    SignerEncodeInfo.cbSize    = sizeof (CMSG_SIGNER_ENCODE_INFO);
                    SignerEncodeInfo.pCertInfo = cert_context->pCertInfo;
                    SignerEncodeInfo.dwKeySpec = ks;
                    SignerEncodeInfo.HashAlgorithm.pszObjId          = pOid;
                    SignerEncodeInfo.HashAlgorithm.Parameters.cbData = NULL;
                    SignerEncodeInfo.pvHashAuxInfo = NULL;
        
                    Signers = SignerEncodeInfo;
        
                    CMSG_SIGNED_ENCODE_INFO SignedMsgEncodeInfo = {0};
        
                    SignedMsgEncodeInfo.cbSize        = sizeof (CMSG_SIGNED_ENCODE_INFO);
                    SignedMsgEncodeInfo.cSigners      = 1;
                    SignedMsgEncodeInfo.rgSigners     = &Signers;
                    SignedMsgEncodeInfo.cCertEncoded  = 1;
                    SignedMsgEncodeInfo.rgCertEncoded = &CertsIncluded;
                    SignedMsgEncodeInfo.rgCrlEncoded  = NULL;
        
                    signed_blob_size = 0;
        
                    signed_blob_size = CryptMsgCalculateEncodedLength ((PKCS_7_ASN_ENCODING | X509_ASN_ENCODING), pFlag, pType, &SignedMsgEncodeInfo, 0, pMessageSize);
        
                    if (signed_blob_size) {
        
                        signed_blob_size *= 2;
        
                        hMsg = CryptMsgOpenToEncode (CERTIFICATE_ENCODING_TYPE,
                                                     pFlag,                   
                                                     pType,                   
                                                     &SignedMsgEncodeInfo, 
                                                     0,                    
                                                     NULL);
        
                        if (hMsg) {
        
                            signed_blob = (BYTE *)malloc (signed_blob_size);
        
                                BOOL CU = CryptMsgUpdate (hMsg, (BYTE *)pMessage, (DWORD)pMessageSize, true);
        
                            if (CU) {
        
                                if (CryptMsgGetParam (
                                        hMsg,               // Handle to the message
                                        CMSG_CONTENT_PARAM, // Parameter type
                                        0,                  // Index
                                        signed_blob,  // Pointer to the BLOB
                                        &signed_blob_size))    // Size of the BLOB
                                {
        
                                    signed_blob = (BYTE *)realloc (signed_blob, signed_blob_size);
        
                                    if (hMsg) {
        
                                        CryptMsgClose (hMsg);
                                        hMsg = 0;
                                    }
                                }
                            }
                            if (hMsg)
                                CryptMsgClose (hMsg);
                            hMsg = 0;
                        }
                    }
        
    CryptReleaseContext (a, 0);

    pSignBlob->cbData = signed_blob_size;
    pSignBlob->pbData = signed_blob;

    CertFreeCertificateContext (cert_context);
    CertCloseStore (store_handle, CERT_CLOSE_STORE_FORCE_FLAG);

    return true;
}

我们使用的oid、flag和type分别是szOID_RSA_SHA1RSA、CMSG_DETACHED_FLAG和CMSG_SIGNED。 将 pSignBlob->pbData 转换为十六进制并将其添加到 /Contents 后,PDF 和签名在 PDF 阅读器中打开时生效。