使用Google KMS和Entrust证书签名的PDF文档 [英] PDF document signing with Google KMS and Entrust certificate

查看:161
本文介绍了使用Google KMS和Entrust证书签名的PDF文档的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过使用CA(Entrust)提供的证书在pdf文档中进行有效签名,该证书是使用Google KMS的私钥生成的(私钥永远不会从KMS发出). 证书链的结构如下:[entrustCert,intermediate,rootCert]

I am trying to make a valid signature in a pdf document by using a certificate from CA (Entrust) generated with a private key from Google KMS (private key never goes out from the KMS). The certificate chain is made as: [entrustCert, intermediate, rootCert]

在我用来实现此目的的代码部分之后:

Following the part of the code I am using to make this happen:

String DEST = "/tmp/test_file.pdf";
OutputStream outputFile = new FileOutputStream(DEST);

CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate[] chain = new X509Certificate[3];
chain[0] = (X509Certificate) certificateFactory.generateCertificate(entrustCert);
chain[1] = (X509Certificate) certificateFactory.generateCertificate(intermediateCert);
chain[2] = (X509Certificate) certificateFactory.generateCertificate(rootCert);
int estimatedSize = 8192;

PdfReader reader = new PdfReader(contract);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

PdfStamper stamper = PdfStamper.createSignature(reader, outputStream, '\0');

PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("reason");
appearance.setLocation("Amsterdam");
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
appearance.setCertificate(chain[0]);

PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);

HashMap<PdfName, Integer> exc = new HashMap<>();
exc.put(PdfName.CONTENTS, (estimatedSize * 2 + 2));
appearance.preClose(exc);

String hashAlgorithm = DigestAlgorithms.SHA256;

BouncyCastleDigest bcd = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, bcd, false);

InputStream data = appearance.getRangeStream();
byte[] hash = DigestAlgorithms.digest(data, MessageDigest.getInstance("SHA-256"));
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);

// Creating signature with Google Cloud KMS
KeyManagementServiceClient client = KeyManagementServiceClient.create();
AsymmetricSignRequest request = AsymmetricSignRequest.newBuilder()
        .setName("path/of/the/key/in/kms")
        .setDigest(Digest.newBuilder().setSha256(ByteString.copyFrom(hash))) 
        .build();
AsymmetricSignResponse r = client.asymmetricSign(request);
byte[] extSignature = r.getSignature().toByteArray();

// Checking if signature is valid
verifySignatureRSA("path/of/the/key/in/kms", hash, extSignature);

sgn.setExternalDigest(extSignature, null, "RSA");
TSAClient tsaClient = new TSAClientBouncyCastle("http://timestamp.entrust.net/...");
estimatedSize += 4192;
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[estimatedSize];
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, (new PdfString(paddedSig)).setHexWriting(true));
appearance.close(dic2);

outputStream.writeTo(outputFile);

这是来自 Google Cloud的函数-创建和验证数字签名以进行签名验证:

This is the function from Google Cloud - Creating and validating digital signatures for the signature verification:

public static boolean verifySignatureRSA(String keyName, byte[] message, byte[] signature)
    throws IOException, GeneralSecurityException {

  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
    com.google.cloud.kms.v1.PublicKey pub = client.getPublicKey(keyName);
    String pemKey = pub.getPem();
    pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", "");
    pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", "");
    pemKey = pemKey.replaceAll("\\s", "");
    byte[] derKey = BaseEncoding.base64().decode(pemKey);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
    PublicKey rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

    Signature rsaVerify = Signature.getInstance("SHA256withRSA");
    rsaVerify.initVerify(rsaKey);
    rsaVerify.update(message);
    return rsaVerify.verify(signature);
  }
}

当前,我遇到以下问题:

Currently I am running in the following issues:

  1. 每个签名均无效:自应用签名以来,文档已被更改或损坏.
  2. 来自Google的签名验证始终为假.

推荐答案

损坏的消息摘要值

file-signed-failed.pdf

Broken message digest value

Analysis of file-signed-failed.pdf

在所包含的签名容器的ASN.1转储中,一个问题立即引起关注:messageDigest属性包含应有签名属性的副本,即具有正确的messageDigest属性:

In an ASN.1 dump of the contained signature container one issue immediately strikes the eye: The messageDigest attribute contains a copy of the signed attributes as they should be, i.e. with a proper messageDigest attribute:

    <30 5C>
4172   92: . . . . . . SEQUENCE {
    <06 09>
4174    9: . . . . . . . OBJECT IDENTIFIER messageDigest (1 2 840 113549 1 9 4)
         : . . . . . . . . (PKCS #9)
    <31 4F>
4185   79: . . . . . . . SET {
    <04 4D>
4187   77: . . . . . . . . OCTET STRING, encapsulates {
    <31 4B>
4189   75: . . . . . . . . . SET {
    <30 18>
4191   24: . . . . . . . . . . SEQUENCE {
    <06 09>
4193    9: . . . . . . . . . . . OBJECT IDENTIFIER
         : . . . . . . . . . . . . contentType (1 2 840 113549 1 9 3)
         : . . . . . . . . . . . . (PKCS #9)
    <31 0B>
4204   11: . . . . . . . . . . . SET {
    <06 09>
4206    9: . . . . . . . . . . . . OBJECT IDENTIFIER data (1 2 840 113549 1 7 1)
         : . . . . . . . . . . . . . (PKCS #7)
         : . . . . . . . . . . . . }
         : . . . . . . . . . . . }
    <30 2F>
4217   47: . . . . . . . . . . SEQUENCE {
    <06 09>
4219    9: . . . . . . . . . . . OBJECT IDENTIFIER
         : . . . . . . . . . . . . messageDigest (1 2 840 113549 1 9 4)
         : . . . . . . . . . . . . (PKCS #9)
    <31 22>
4230   34: . . . . . . . . . . . SET {
    <04 20>
4232   32: . . . . . . . . . . . . OCTET STRING    
         : . . . . . . . . . . . . . 40 76 BC 3F 05 25 E4 C3    @v.?.%..
         : . . . . . . . . . . . . . 27 AD 78 FA 73 31 4C 1B    '.x.s1L.
         : . . . . . . . . . . . . . 82 97 3D AA 4E 81 72 D6    ..=.N.r.
         : . . . . . . . . . . . . . 23 3C DD 59 D2 82 81 55                            
         : . . . . . . . . . . . . }
         : . . . . . . . . . . . }
         : . . . . . . . . . . }
         : . . . . . . . . . }
         : . . . . . . . . }
         : . . . . . . . }

事实上,在您的代码中原因很明确:

And indeed, in your code the reason becomes clear:

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
[...]
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);

这两个调用必须必须具有相同的参数(除了在第二个位置添加的tsaClient之外),因为在第一次调用中检索到了经过身份验证的属性(又名带符号的属性)是实际签名的字节,因此必须使用相同的输入创建最终的签名容器才能有效.

These two calls must have the same parameters (except the added tsaClient at second position) because the authenticated attributes (aka signed attributes) retrieved in the first call are the actually signed bytes, so the final signature container must be created with the same inputs to be valid.

(如果您的变量名更清楚,则可能早点发现了该问题.)

(If your variable names had been clearer, that problem might have been spotted earlier.)

因此,要修复已签名的属性,请替换

Thus, to fix the signed attributes, replace

byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);

作者

byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);

RSA填充

已经解决了上述问题,出现了一个新问题,内部密码库错误.错误代码:0x2726"

test_file.pdf

使用签名者证书的公共RSA密钥解密签名字节会导致

Decrypting the signature bytes using the public RSA key of the signer certificate resulted in

2D9B224E0894E73B1D3EDEE43E5C34A152057B008518538F3D6DA9C5AC73B54AEF33EB165ED0815F2E7851C86308AAFEC3FC0CD5CA77D7A745C056CB37783B7B51484D9B6C1F6D7E42C2B1C49127CD7D1C3A371D943A5C6F5DDA47C758493D2D3CA7D165B35A1BE4FA590911E801D7026822A9B9D202AE9A671DF4F36D42AAD712D43506EC3607E5AC7CCE23389BE288DD32C9C45B92CAA7225897EFD9F8ECFE2A40007FD6AC8B625239E6E529B7521E2EB652659A8F8B3F7262D46E8A0207A3004FEF48C87FC8A52B632268FDD0888A00AE6A3B303A138B18F28A66108467BFF743A859ECD193ADB52268B1FC531690B99D35D5E68BF804B59E24FCB180FABC

这显然看起来不像是PKCS1v1.5填充哈希值.因此,所指称的签名者证书有误,或者我们实质上看到了垃圾,或者签名根本不使用PKCS1v1.5填充,而是使用PSS填充.后面的BC是后者的指示符,但垃圾也可能以BC结尾.

This clearly does not look like a PKCS1v1.5 padded hash value. Thus, either the alleged signer certificate is wrong and we see essentially garbage or the signature does not use PKCS1v1.5 padding at all but instead PSS padding. The trailing BC is an indicator for the latter but garbage may also end in BC.

尽管如此,OP已确认:

Meanwhile, though, the OP has confirmed:

在Google KMS中生成的私钥是2048位RSA密钥PSS填充-SHA256摘要

the private key generated in Google KMS is 2048 bit RSA key PSS Padding - SHA256 Digest

这确实说明了签名的问题:iText 5.x确实不支持RSASSA-PSS.创建RSA签名时,它会自动采用PKCS1v1.5填充;特别是在CMS签名容器中,它生成它表示签名算法为RSASSA-PKCS1-v1_5.因此,任何验证器都将无法验证签名.

This indeed explain the issue with the signature: iText 5.x does not support RSASSA-PSS. When creating a RSA signature it automatically assumes PKCS1v1.5 padding; in particular in the CMS signature container it generates it denotes that the signing algorithm is RSASSA-PKCS1-v1_5. Thus, any validator will fail validating the signature.

显而易见的选择是

  • 通过拉皮条或替换iText PdfPKCS7类来添加RSASSA-PSS支持
  • 或切换到RSASSA-PKCS1-v1_5键.
  • either add RSASSA-PSS support by pimping or replacing the iText PdfPKCS7 class
  • or switch to a RSASSA-PKCS1-v1_5 key.

这篇关于使用Google KMS和Entrust证书签名的PDF文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆