Bouncy Castle:Pemreader => Pemparser

PEMParser hanging with no exception thrown

I'm no expert, but what you are showing does not look like a key in PEM format.

Compare with: http://www.herongyang.com/Cryptography/Certificate-Format-PEM-on-Certificates.html

This may help: https://sycure.wordpress.com/2008/05/15/tips-using-openssl-to-extract-private-key-pem-file-from-pfx-personal-information-exchange/

Reading encrypted private key in PKCS#8 format through bouncycastle, Java failing in docker container

Like @Bragolgirith suspected, BouncyCastle seems to have problems with OpenJ9. I guess it is not a Docker issue, because I can reproduce it on GitHub Actions, too. It is also not limited to BouncyCastle 1.64 or 1.70, it happens in both versions. It also happens on OpenJ9 JDK 11, 14, 17 on Windows, MacOS and Linux, but for the same matrix of Java and OS versions it works on Adopt-Hotspot and Zulu.

Here is an example Maven project and a failed matrix build. So if you select another JVM type, you should be fine. I know that @Bragolgirith already suggested that, but I wanted to make the problem reproducible for everyone and also provide an MCVE, in case someone wants to open a BC or OpenJ9 issue.

P.S.: It is also not a character set issue with the InputStreamReader. This build fails exactly the same as before after I changed the constructor call.


Update: I have created BC-Java issue #1099. Let's see what the maintainers can say about this.


Update 2: The solution to your problem is to explicitly set the security provider to BC for your input decryptor provider. Thanks to David Hook for his helpful comment in #1099.

BouncyCastleProvider securityProvider = new BouncyCastleProvider();
Security.addProvider(securityProvider);

// (...)

InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder()
// Explicitly setting security provider helps to avoid ambiguities
// which otherwise can cause problems, e.g. on OpenJ9 JVMs
.setProvider(securityProvider)
.build(passphrase.toCharArray());

See this commit and the corresponding build, now passing on all platforms, Java versions and JVM types (including OpenJ9).

Because @Bragolgirith mentioned it in his answer: If you want to avoid the explicit new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(securityProvider), the call Security.insertProviderAt(securityProvider, 1) instead of simply Security.addProvider(securityProvider) would in this case also solve the problem. But this holds true only as long as no other part of your code or any third-party library sets another provider to position 1 afterwards, as explained in the Javadoc. So maybe it is not a good idea to rely on that.

Bouncy castle create a certificate from openssl csr in java

The answer you linked tells you what the code returns: "a valid PEM-encoded signedData object containing a signed Certificate chain (of the type that can be imported by keytool)". Actually it's not quite valid: (1) it doesn't put any linebreaks in the base64 as PEM officially requires -- which you fixed, although you did it at 63 not 64 as standard; (2) it uses BEGIN/END PKCS #7 SIGNED DATA whereas the standard is BEGIN/END PKCS7 -- or in most cases BEGIN/END CMS because CMS is basically a superset of PKCS7. keytool ignores both of these flaws when reading a 'CA reply' (i.e. -importcert to an existing privateKey entry), but other software doesn't -- like BouncyCastle and OpenSSL. You already added line breaks; if you change the BEGIN/END labels to PKCS7 and put the result in a file you can read it with among other things

 openssl pkcs7 -in $file -print_certs # add -text for expanded details

However, calling this 'the type that can be imported by keytool' implies it is the only type which is false. keytool can read PKCS7 containing certs -- either PEM or DER -- but it can also read 'bare' X.509 certificates in either PEM or DER, and that is usually more convenient. To do that, replace everything from CMSSignedDataGenerator onward with something like for DER:

Files.write(Paths.get("outputfile"), certencoded)

or for PEM since you have Bouncy:

try( PemWriter w = new PemWriter(new FileWriter("outputfile")) ){
w.writeObject(new PemObject("CERTIFICATE",certencoded));
}

or if you prefer to do it yourself:

try( Writer w = new FileWriter("outputfile") ){
w.write("-----BEGIN CERTIFICATE-----\r\n"
+ Base64.getMimeEncoder().encodeToString(certencoded)
// MimeEncoder does linebreaks at 76 by default which
// is close enough and less work than doing it by hand
+ "\r\n-----END CERTIFICATE-----\r\n");
}

Note: you may not need the \r -- PEM doesn't actually specify CRLF vs LF linebreaks -- but MimeEncoder uses them on the interior breaks and I like to be consistent.

This -- just the certificate -- is what your openssl x509 -req -CA* alternative produces, and that's why it's much smaller -- one cert is smaller than three certs plus some overhead.

Several more points:

  • PEMParser can handle CSR just fine; you don't need the PemReader+PemObject detour

  • using the entire SubjectPublicKeyInfo for the SubjectKeyIdentifier extension is wrong. OTOH nothing uses the SKI extension in a leaf cert anyway, so simplest to just delete this rather than fix it.

  • in certgen.build() if you use JcaContentSignerBuilder instead of the BcRSA variant, you can pass cakey directly without going through PrivateKeyFactory and the signature scheme directly without going through two AlgorithmIdentifierFinders -- as the /*cms-sd*/generator.addSignerInfoGenerator later in the code already does.

  • speaking of the signature scheme, don't use SHA1 for cert signatures (or most others). It was already deprecated in 2013 when that answer was written, and in 2017 was publicly broken for collision (see https://shattered.io) after which many systems either reject it entirely or nag mercilessly for such use, and some related ones like git. In fact many authorities and checklisters now prohibit it for any use, even though some (like HMAC and PBKDF) aren't actually broken. Your openssl x509 -req alternative used SHA256 which is fine*.

  • and lastly, issuing a cert is NOT 'sign[ing] {the|a} CSR'. You can just look at the cert contents and see it's not at all the same as the CSR or even the CSR body. You create (or generate as used in the Bouncy names) a cert and sign the cert -- in response to a CSR.

* at least for now; if and when quantum cryptanalysis works a lot of currently used schemes, including probably SHA256-RSA signature, will be in trouble and will have to be replaced by 'post-quantum' schemes which people are now working to develop. But if this happens, you'll see it on every news channel and site in existence.

Loading previously stored Keys fails in BouncyCastle with Java

Bouncy castle is writting PEM file in PKCS8 format, not PKCS1, and it never let you know that it didn't do what you were expecting.

I don't know how to write PEM file in PKCS1 format. I would love to know, because I periodically spend an afternoon trying to do so before convincing myself that PCKS8 is just so better and superior, and no one need PKCS1 really, and just nobody talk me about PKCS1 again.

So if you use the openssl command:

openssl pkcs8 -topk8 -nocrypt -in private.pem

in place of the one I suppose you tried (openssl rsa -in private.pem -check), you will get the same content as what bouncy castle wrote. Here again, we have a command which is just a little to smart about what it does, and don't tell you that "yeah, you tell me to read pcks1 RSA file, but look, headers are always lying, and I DO see that it's in fact pkcs8, so I will just read that as pkcs8 and everybody is happy right?"

And so, your code should be adapted to read PCKS8 to something like (I didn't do Java since 10y, so perhaps there's some little things to adapt):

public static PemObject createPrivateObject(KeyPair key) throws Exception {
return new PemObject("PRIVATE KEY", key.getPrivate().getEncoded());
}

[...]

public static KeyPair readKeyPair(String path) {
File privateKeyFile = new File(path);
try (PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile))){

PrivateKeyInfo privkeyInfo = (PrivateKeyInfo)pemParser.readObject();
PKCS8EncodedKeySpec keyspec = new PKCS8EncodedKeySpec(privkeyInfo.getEncoded);
RSAPrivateKey privKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keyspec)
pemParser.close();
return kp;

} catch { ....


Related Topics



Leave a reply



Submit