Skip to content

Add support for PKCS#1 keys#7451

Closed
floragunn wants to merge 5 commits intonetty:4.1from
floragunncom:pkcs1_support
Closed

Add support for PKCS#1 keys#7451
floragunn wants to merge 5 commits intonetty:4.1from
floragunncom:pkcs1_support

Conversation

@floragunn
Copy link
Copy Markdown

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.

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.
@normanmaurer
Copy link
Copy Markdown
Member

private static final InternalLogger logger = InternalLoggerFactory.getInstance(BouncyCastlePemReader.class);
private static Boolean available;

public static boolean isBcAvailable() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove Bc ?

return available;
}

private static void tryLoadingBc() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Bc

if (pemParser != null) {
try {
pemParser.close();
} catch (Exception e) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e -> ignored

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what else can we do here instead of ignoring?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@floragunncom I only asked to rename e to ignored ;)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may also want to log something in to debug level

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to "ignored"

return new PEMParser(new FileReader(keyFile));
}

private static PEMParser getParser(InputStream keyInputStream) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/getParser/newParser/

}
}

private static PEMParser getParser(File keyFile) throws FileNotFoundException {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/getParser/newParser/

try {
return getPrivateKey(getParser(keyFile), keyPassword);
} catch (Exception e) {
logger.debug("Unable to extract private key due to " + e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to: logger.debug("Cannot load Bouncy Castle provider", e);

}

static {
isBcAvailable();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call tryLoadingBc

Copy link
Copy Markdown
Member

@normanmaurer normanmaurer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only a few more small things

private static Boolean available;

public static boolean isAvailable() {
tryLoading();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this as tryLoading() is called in static {...}

}

private static void tryLoading() {
if (available != null) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the , on the line above.


private static PrivateKey getPrivateKey(PEMParser pemParser, String keyPassword) throws PEMException, IOException,
PKCSException, OperatorCreationException {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove empty line

= (Class<Provider>) Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider",
true, this.getClass().getClassLoader());
if (Security.getProvider("BC") == null) {
Security.addProvider(bcProviderClass.newInstance());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getConstructor().newInstance()

logger.debug("Bouncy Castle provider available");
return Boolean.TRUE;
} catch (Exception e) {
logger.debug("Cannot load Bouncy Castle provider", e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception / Errors should be saved, likely in some sort of bouncyCastleUnavailabilityCause field for later use.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. Look at the native transports for an example of this (e.g. KQueue)

private static Boolean available;

static {
tryLoading();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check is available first? Maybe throw the unavailability cause here?

});
}

public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value needs to be documented.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also document the parameters too please.

try {
pemParser.close();
} catch (Exception ignored) {
// ignore
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be logged. debug level is probably okay.

});
}

public static PrivateKey getPrivateKey(InputStream keyInputStream, String keyPassword) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also document the parameters too please.

}
}

private static PrivateKey getPrivateKey(PEMParser pemParser, String keyPassword) throws PEMException, IOException,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 "
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 "
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean that BouncyCastle will be used for PKCS8 keys too?

}

// try BC first, if this fail fallback to original key extraction
// process
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting: move this comment onto the previous line

}

// try BC first, if this fail fallback to original key extraction
// process
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting: move this comment onto the previous line

@normanmaurer
Copy link
Copy Markdown
Member

@floragunncom can you please address comments of @Scottmitch and @carl-mastrangelo and ping me once done. Thanks!

@normanmaurer
Copy link
Copy Markdown
Member

@floragunncom ping

@floragunn
Copy link
Copy Markdown
Author

no cycles at the moment but will come back to this in the next weeks

@normanmaurer
Copy link
Copy Markdown
Member

@floragunncom any update ?

@normanmaurer
Copy link
Copy Markdown
Member

Let me close this due inactivity. Please reopen once you have time again.

@4x0v7
Copy link
Copy Markdown

4x0v7 commented Sep 27, 2019

Hi there. I pieced together some code to import PKCS#1 RSA keys into a KeyStore. Could this be useful here?

@njhill
Copy link
Copy Markdown
Member

njhill commented Sep 30, 2019

Thanks @4x0v7 I think it would be a nice addition, would you be willing to open a PR?

@4x0v7
Copy link
Copy Markdown

4x0v7 commented Oct 2, 2019

@njhill Yes I can do. The implementation I did only supports PKCS#1 RSA private keys, and doesn't use bouncycastle

@normanmaurer
Copy link
Copy Markdown
Member

@4x0v7 yep please open a PR and let us know once ready for review :)

@cwperks cwperks mentioned this pull request Aug 5, 2022
normanmaurer pushed a commit that referenced this pull request Aug 10, 2022
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>
normanmaurer pushed a commit that referenced this pull request Aug 10, 2022
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Support PKCS#1 keys when dealing with certificates

7 participants