Conversation
Motivation: Currently netty only support PKCS#8 keys, but PKCS#1 is still used in the wild out there. This only works if Bouncy Castle (BC) is available in the classpath. Modifications: - Until now netty implements the extraction of a key from a PEM itself. All this code remains and is still valid. - Before the original code is called we look if BC is available. If not then we do just nothing. If yes we use BC to extract the private key from the PEM. If that fails we fallback to the original code. - Add unittests for unencrypted and encrypted PKCS#1 PEM formatted keys. Result: Fixes netty#7323.
|
@floragunncom please fix the test error: https://garage.netty.io/teamcity/viewLog.html?buildId=26426&buildTypeId=pulls_netty&guest=1 |
| private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastlePemReader.class); | ||
| private static Boolean available; | ||
|
|
||
| public static boolean isBcAvailable() { |
| return available; | ||
| } | ||
|
|
||
| private static void tryLoadingBc() { |
| if (pemParser != null) { | ||
| try { | ||
| pemParser.close(); | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
what else can we do here instead of ignoring?
There was a problem hiding this comment.
@floragunncom I only asked to rename e to ignored ;)
There was a problem hiding this comment.
You may also want to log something in to debug level
| return new PEMParser(new FileReader(keyFile)); | ||
| } | ||
|
|
||
| private static PEMParser getParser(InputStream keyInputStream) { |
| } | ||
| } | ||
|
|
||
| private static PEMParser getParser(File keyFile) throws FileNotFoundException { |
| try { | ||
| return getPrivateKey(getParser(keyFile), keyPassword); | ||
| } catch (Exception e) { | ||
| logger.debug("Unable to extract private key due to " + e); |
There was a problem hiding this comment.
change to: logger.debug("Unable to extract private key", e);
This way we also get a stacktrace
| try { | ||
| return getPrivateKey(getParser(keyInputStream), keyPassword); | ||
| } catch (Exception e) { | ||
| logger.debug("Unable to extract private key due to " + e); |
There was a problem hiding this comment.
change to: logger.debug("Unable to extract private key", e);
This way we also get a stacktrace
| logger.debug("Bouncy Castle provider available"); | ||
| return Boolean.TRUE; | ||
| } catch (Exception e) { | ||
| logger.debug("Cannot load Bouncy Castle provider"); |
There was a problem hiding this comment.
Change to: logger.debug("Cannot load Bouncy Castle provider", e);
| } | ||
|
|
||
| static { | ||
| isBcAvailable(); |
normanmaurer
left a comment
There was a problem hiding this comment.
only a few more small things
| private static Boolean available; | ||
|
|
||
| public static boolean isAvailable() { | ||
| tryLoading(); |
There was a problem hiding this comment.
You can remove this as tryLoading() is called in static {...}
| } | ||
|
|
||
| private static void tryLoading() { | ||
| if (available != null) { |
There was a problem hiding this comment.
remove this as it should only be called one time.
| try { | ||
| Class<Provider> bcProviderClass | ||
| = (Class<Provider>) Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider" | ||
| , true, this.getClass().getClassLoader()); |
There was a problem hiding this comment.
move the , on the line above.
|
|
||
| private static PrivateKey getPrivateKey(PEMParser pemParser, String keyPassword) throws PEMException, IOException, | ||
| PKCSException, OperatorCreationException { | ||
|
|
| = (Class<Provider>) Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider", | ||
| true, this.getClass().getClassLoader()); | ||
| if (Security.getProvider("BC") == null) { | ||
| Security.addProvider(bcProviderClass.newInstance()); |
There was a problem hiding this comment.
Even if bouncycastle is on the classpath, I do not think a library like Netty should be installing this provider for everything in the JVM. I'd suggest passing an instance of the provider to methods as needed.
There was a problem hiding this comment.
Thats indeed a valid argument, i will change this
| Class<Provider> bcProviderClass | ||
| = (Class<Provider>) Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider", | ||
| true, this.getClass().getClassLoader()); | ||
| bcProvider = bcProviderClass.newInstance(); |
There was a problem hiding this comment.
getConstructor().newInstance()
| logger.debug("Bouncy Castle provider available"); | ||
| return Boolean.TRUE; | ||
| } catch (Exception e) { | ||
| logger.debug("Cannot load Bouncy Castle provider", e); |
There was a problem hiding this comment.
Exception / Errors should be saved, likely in some sort of bouncyCastleUnavailabilityCause field for later use.
There was a problem hiding this comment.
+1. Look at the native transports for an example of this (e.g. KQueue)
| private static Boolean available; | ||
|
|
||
| static { | ||
| tryLoading(); |
There was a problem hiding this comment.
nit: doing this in a static init block leads to problems. If any exceptions are thrown, they are lost and reinterpreted as ClassNotFoundExceptions. Avoid if possible. You can delay till the first method call.
| } | ||
|
|
||
| public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) { | ||
| try { |
There was a problem hiding this comment.
check is available first? Maybe throw the unavailability cause here?
| }); | ||
| } | ||
|
|
||
| public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) { |
There was a problem hiding this comment.
The return value needs to be documented.
There was a problem hiding this comment.
also document the parameters too please.
| try { | ||
| pemParser.close(); | ||
| } catch (Exception ignored) { | ||
| // ignore |
There was a problem hiding this comment.
This should be logged. debug level is probably okay.
| }); | ||
| } | ||
|
|
||
| public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) { |
There was a problem hiding this comment.
also document the parameters too please.
| } | ||
| } | ||
|
|
||
| private static PrivateKey getPrivateKey(PEMParser pemParser, String keyPassword) throws PEMException, IOException, |
There was a problem hiding this comment.
IOException covers PEMException, and so you can remove PEMException.
| PKCSException, OperatorCreationException { | ||
| try { | ||
| Object object = pemParser.readObject(); | ||
| logger.debug("Parsed PEM object of type " + object.getClass().getName() + " and assume key is " |
There was a problem hiding this comment.
is this debug statement necessary? if so can you use the slf4j logging format to avoid always doing string concatenation operations (e.g. "Parsed PEM object of type {} ...", object.getClass().getName()
| } else if (object instanceof PEMKeyPair) { | ||
| pk = converter.getKeyPair((PEMKeyPair) object).getPrivate(); | ||
| } else { | ||
| logger.debug("Unable to handle PEM object of type " |
There was a problem hiding this comment.
use slf4j's formatting syntax to avoid always doing string concatenation.
| pk = converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object) | ||
| .decryptPrivateKeyInfo(pkcs8InputDecryptorProvider)); | ||
| } else { | ||
| logger.debug("Unable to handle PEM object of type " + object.getClass() + " as a encrypted key"); |
There was a problem hiding this comment.
use slf4j's formatting syntax to avoid always doing string concatenation.
| logger.debug("Bouncy Castle provider available"); | ||
| return Boolean.TRUE; | ||
| } catch (Exception e) { | ||
| logger.debug("Cannot load Bouncy Castle provider", e); |
There was a problem hiding this comment.
+1. Look at the native transports for an example of this (e.g. KQueue)
| public final class BouncyCastlePemReader { | ||
|
|
||
| private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastlePemReader.class); | ||
| private static Provider bcProvider; |
There was a problem hiding this comment.
if these variables are initialized in a static context, then we should be able to make them final.
| .setProvider(bcProvider) | ||
| .build(keyPassword.toCharArray()); | ||
| pk = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate(); | ||
| } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { |
There was a problem hiding this comment.
does this mean that BouncyCastle will be used for PKCS8 keys too?
| } | ||
|
|
||
| // try BC first, if this fail fallback to original key extraction | ||
| // process |
There was a problem hiding this comment.
formatting: move this comment onto the previous line
| } | ||
|
|
||
| // try BC first, if this fail fallback to original key extraction | ||
| // process |
There was a problem hiding this comment.
formatting: move this comment onto the previous line
|
@floragunncom can you please address comments of @Scottmitch and @carl-mastrangelo and ping me once done. Thanks! |
|
@floragunncom ping |
|
no cycles at the moment but will come back to this in the next weeks |
|
@floragunncom any update ? |
|
Let me close this due inactivity. Please reopen once you have time again. |
|
Hi there. I pieced together some code to import PKCS#1 RSA keys into a |
|
Thanks @4x0v7 I think it would be a nice addition, would you be willing to open a PR? |
|
@njhill Yes I can do. The implementation I did only supports PKCS#1 RSA private keys, and doesn't use bouncycastle |
|
@4x0v7 yep please open a PR and let us know once ready for review :) |
Motivation: Motivated by a stale [PR](#7451) that was closed. This change adds support for keys in the PKCS#1 format. Currently netty only supports PKCS#8 keys. Modification: This change introduces a class called `BouncyCastlePemReader` which is only used if BouncyCastle is available on the classpath and uses BouncyCastle's PEMParser to parse the private keys. See list of supported types [here](https://www.bouncycastle.org/docs/pkixdocs1.5on/org/bouncycastle/openssl/PEMParser.html). Tests are added in `SSLContextTest` including tests with PKCS#8 keys (encrypted + unencrypted) to show these working with BouncyCastle. Result: Fixes [#7323](#7323) Signed-off-by: Craig Perkins <cwperx@amazon.com>
Motivation: Motivated by a stale [PR](#7451) that was closed. This change adds support for keys in the PKCS#1 format. Currently netty only supports PKCS#8 keys. Modification: This change introduces a class called `BouncyCastlePemReader` which is only used if BouncyCastle is available on the classpath and uses BouncyCastle's PEMParser to parse the private keys. See list of supported types [here](https://www.bouncycastle.org/docs/pkixdocs1.5on/org/bouncycastle/openssl/PEMParser.html). Tests are added in `SSLContextTest` including tests with PKCS#8 keys (encrypted + unencrypted) to show these working with BouncyCastle. Result: Fixes [#7323](#7323) Signed-off-by: Craig Perkins <cwperx@amazon.com>
Motivation:
Currently netty only support PKCS#8 keys,
but PKCS#1 is still used in the wild out there.
This only works if Bouncy Castle (BC) is available in the
classpath.
Modifications:
Until now netty implements the extraction of a key from a PEM
itself. All this code remains and is still valid.
Before the original code is called we look if BC is available.
If not then we do just nothing. If yes we use BC to extract the
private key from the PEM. If that fails we fallback to the original code.
Add unittests for unencrypted and encrypted PKCS#1 PEM formatted keys.
Result:
Fixes #7323.