Estoy tratando de hacer una firma válida en un documento PDF mediante el uso de un certificado de CA (Entrust) generado con una clave privada de Google KMS (clave privada nunca se apaga desde el KMS). La cadena de certificados se hace como: [entrustCert, intermedio, rootCert]
Después de la parte del código que estoy utilizando para hacer que esto ocurra:
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);
Esta es la función de la nube de Google - Creación y validación de firmas digitales para la verificación de la firma:
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);
}
}
Estoy actualmente en ejecución en las siguientes cuestiones:
- Cada firma no es válida: El documento ha sido alterado o dañado desde que se aplicó la firma.
- La verificación de la firma de Google siempre es falsa.
mensaje roto valor de resumen
El análisis de archivo de firma-failed.pdf
En un volcado ASN.1 de la cuestión recipiente una firma contenida enseguida llama la atención: El messageDigest
atributo contiene una copia de los atributos firmados como debe ser, es decir, con una adecuada messageDigest
atributo:
<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
: . . . . . . . . . . . . }
: . . . . . . . . . . . }
: . . . . . . . . . . }
: . . . . . . . . . }
: . . . . . . . . }
: . . . . . . . }
Y de hecho, en el código de la razón se convierte en claro:
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
[...]
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
Estas dos llamadas deben tener los mismos parámetros (excepto el añadido tsaClient
en segunda posición), porque los atributos autentificados (atributos aka firmados) recuperados en la primera convocatoria están los bytes realmente firmados, por lo que el contenedor de la firma final debe ser creado con las mismas entradas que sean válido.
(Si los nombres de las variables habían sido más claro, el problema podría haber sido visto antes.)
Por lo tanto, para fijar los atributos firmados, reemplace
byte[] encodedSig = sgn.getEncodedPKCS7(sh, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
por
byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, null, null, MakeSignature.CryptoStandard.CMS);
acolchado de RSA
Después de haber trabajado en solucionar el problema anterior, un nuevo problema se presentó, "error de biblioteca criptográfica interna Código de error:. 0x2726"
Análisis de test_file.pdf
Descifrar el bytes de firma utilizando la clave RSA pública del certificado del firmante dio lugar a
2D9B224E0894E73B1D3EDEE43E5C34A152057B008518538F3D6DA9C5AC73B54AEF33EB165ED0815F2E7851C86308AAFEC3FC0CD5CA77D7A745C056CB37783B7B51484D9B6C1F6D7E42C2B1C49127CD7D1C3A371D943A5C6F5DDA47C758493D2D3CA7D165B35A1BE4FA590911E801D7026822A9B9D202AE9A671DF4F36D42AAD712D43506EC3607E5AC7CCE23389BE288DD32C9C45B92CAA7225897EFD9F8ECFE2A40007FD6AC8B625239E6E529B7521E2EB652659A8F8B3F7262D46E8A0207A3004FEF48C87FC8A52B632268FDD0888A00AE6A3B303A138B18F28A66108467BFF743A859ECD193ADB52268B1FC531690B99D35D5E68BF804B59E24FCB180FABC
Es claro que esto no se ve como un valor hash acolchada PKCS1v1.5. Por lo tanto, ya sea el presunto certificado del firmante está mal y vemos esencialmente de basura o la firma no utiliza el relleno PKCS1v1.5 en absoluto, pero en lugar de relleno PSS. El arrastre BC
es un indicador para la última pero basura también puede terminar en BC
.
Mientras tanto, sin embargo, el PO ha confirmado:
la clave privada generada en Google KMS es la clave RSA de 2048 bits de relleno PSS - SHA256 Digesto
Este hecho explica el problema con la firma: iText 5.x no no admite RSASSA-PSS. Al crear una firma RSA asume automáticamente el relleno PKCS1v1.5; en particular, en el recipiente de la firma CMS genera denota que el algoritmo de firma es RSASSA PKCS1-v1_5. Por lo tanto, cualquier validador fallará la validación de la firma.
Las opciones son obvias a
- o bien añadir soporte RSASSA-PSS por proxenetismo o reemplazar la iText
PdfPKCS7
clase - o cambie a una clave RSASSA PKCS1-v1_5.