[英]PDF document signing with Google KMS and Entrust certificate
我正在嘗試通過使用CA(Entrust)提供的證書在pdf文檔中進行有效簽名,該證書是使用Google KMS的私鑰生成的(私鑰永遠不會從KMS發出)。 證書鏈的構成如下:[entrustCert,intermediate,rootCert]
按照我用來實現此目標的代碼部分:
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的功能-創建和驗證數字簽名以進行簽名驗證:
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);
}
}
目前,我正在處理以下問題:
在所包含的簽名容器的ASN.1轉儲中,一個問題立即引起關注: messageDigest
屬性包含應有的簽名屬性的副本,即具有正確的messageDigest
屬性:
<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
: . . . . . . . . . . . . }
: . . . . . . . . . . . }
: . . . . . . . . . . }
: . . . . . . . . . }
: . . . . . . . . }
: . . . . . . . }
實際上,在您的代碼中原因很明確:
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
[...]
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
這兩個調用必須具有相同的參數 (除了在第二個位置添加的tsaClient
之外),因為在第一個調用中檢索到的身份驗證屬性(又名帶符號的屬性)是實際帶符號的字節,因此必須使用相同的輸入創建最終的簽名容器。有效。
(如果您的變量名更加清楚,則可能早就發現了該問題。)
因此,要修復已簽名的屬性,請替換
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
通過
byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
解決了上述問題后,出現了一個新問題, “內部密碼庫錯誤。錯誤代碼:0x2726”
使用簽名者證書的公共RSA密鑰解密簽名字節導致
2D9B224E0894E73B1D3EDEE43E5C34A152057B008518538F3D6DA9C5AC73B54AEF33EB165ED0815F2E7851C86308AAFEC3FC0CD5CA77D7A745C056CB37783B7B51484D9B6C1F6D7E42C2B1C49127CD7D1C3A371D943A5C6F5DDA47C758493D2D3CA7D165B35A1BE4FA590911E801D7026822A9B9D202AE9A671DF4F36D42AAD712D43506EC3607E5AC7CCE23389BE288DD32C9C45B92CAA7225897EFD9F8ECFE2A40007FD6AC8B625239E6E529B7521E2EB652659A8F8B3F7262D46E8A0207A3004FEF48C87FC8A52B632268FDD0888A00AE6A3B303A138B18F28A66108467BFF743A859ECD193ADB52268B1FC531690B99D35D5E68BF804B59E24FCB180FABC
顯然,這看起來不像PKCS1v1.5填充哈希值。 因此,所指稱的簽名者證書有誤,或者我們基本上看到了垃圾,或者簽名根本沒有使用PKCS1v1.5填充,而是使用了PSS填充。 尾隨的BC
是后者的指示,但垃圾也可能以BC
結尾。
同時,OP已確認:
Google KMS中生成的私鑰是2048位RSA密鑰PSS填充-SHA256摘要
這確實與簽名解釋這個問題:iText的5.x中不支持RSASSA-PSS。 創建RSA簽名時,它會自動采用PKCS1v1.5填充; 特別是在CMS簽名容器中,它生成它表示簽名算法為RSASSA-PKCS1-v1_5。 因此,任何驗證器都將無法驗證簽名。
顯而易見的選擇是
PdfPKCS7
類來添加RSASSA-PSS支持
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.