user1120897
user1120897

Reputation: 272

Encode PKCS7 with Bouncycastle

My code relies on sun.security to generate a PKCS7 signature block for signing an APK:

    private static void writeSignatureBlock(byte[] signatureBytes, X509Certificate publicKey, OutputStream out)
            throws Exception {
        SignerInfo signerInfo = new SignerInfo(new X500Name(publicKey.getIssuerX500Principal().getName()),
                publicKey.getSerialNumber(), AlgorithmId.get("SHA1"), AlgorithmId.get("RSA"), signatureBytes);

        PKCS7 pkcs7 = new PKCS7(new AlgorithmId[] { AlgorithmId.get("SHA1") },
                new ContentInfo(ContentInfo.DATA_OID, null), new X509Certificate[] { publicKey },
                new SignerInfo[] { signerInfo });

        ByteArrayOutputStream o = new ByteArrayOutputStream();
        pkcs7.encodeSignedData(o);
        byte[] expected = o.toByteArray();
    }

I am trying to replace this with Bouncy castle, since newer Java versions do not like accesses to sun's internal classes.

In order to replicate this behavior with bouncy castle, I tried this code:

        List<java.security.cert.Certificate> certList = new ArrayList<java.security.cert.Certificate>();
        java.security.cert.Certificate certificate = publicKey;
        certList.add(certificate);
        JcaCertStore certs = new JcaCertStore(certList);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        gen.addCertificates(certs);

        final byte[] signedHash = signatureBytes;

        ContentSigner nonSigner = new ContentSigner() {

            @Override
            public byte[] getSignature() {
                return signedHash;
            }

            @Override
            public OutputStream getOutputStream() {
                return new ByteArrayOutputStream();
            }

            @Override
            public AlgorithmIdentifier getAlgorithmIdentifier() {
                return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1WithRSA");
            }

        };

        org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate
                .getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded()));
        JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().build());
        sigb.setDirectSignature(true);
        gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(cert)));
        CMSProcessableByteArray msg = new CMSProcessableByteArray(new byte[0]);

        CMSSignedData signedData = gen.generate(msg, false);
        byte[] bouncyCastle = signedData.getEncoded();

The generated outputs have different lengths and are quite different:

Expected length: 1489
BouncyCastle length: 1491```

Why do these outputs differ in length (if only the content would be different I'd assume I supplied the data in a wrong format)?

Upvotes: 0

Views: 1416

Answers (2)

Heri
Heri

Reputation: 4578

Alternative to BouncyCastle is the Osdt CERT library from Oracle, available from Maven-Repository. Gradle-dependency:

// https://mvnrepository.com/artifact/com.oracle.ojdbc/osdt_core
implementation "com.oracle.ojdbc:osdt_cert:19.3.0.0" 

The Oracle library is IMO much simpler to use then Bouncy castle. Producing a PKCS7 DER format from an existing X509 format is a matter 3 code lines:

import oracle.security.crypto.cert.PKCS7;
import oracle.security.crypto.cert.X509;

        byte[] certDER = <retrieve the X.509 certificate as DER encoded byte array>
        X509 x509 = new X509(certAsDER);
        PKCS7 pkcs7 = new PKCS7(x509);
        byte pkcs7DER = pkcs7.getEncoded();

Upvotes: 0

President James K. Polk
President James K. Polk

Reputation: 41958

The only difference is that Bouncycastle uses a slightly different encoding of certain strings in the certificate subject than sun.* classes. Bouncycastle is more in line with RFC 3280, but RFC5280 relaxed the requirements somewhat. Either encoding should be fine.

Upvotes: 1

Related Questions