Skip to content

Commit

Permalink
Allow InputStreams for key/trust managers in SslContextBuilder
Browse files Browse the repository at this point in the history
Motivation:

Sometimes it's easier to get keys/certificates as `InputStream`s than it is to
get an actual `File`. This is especially true when operating in a container
environment and `getResourceAsInputStream` is the best way to load resources
packaged with an application.

Modifications:

- Add read-from-`InputStream` methods to `PemReader`
- Allow `SslContext` to get keys/certificates from `InputStreams`
- Add `InputStream`-based setters for key/trust managers to `SslContextBuilder`

Result:

Callers may pass an `InputStream` instead of a `File` to `SslContextBuilder`.
  • Loading branch information
jchambers authored and Scottmitch committed Feb 5, 2016
1 parent 64377fe commit 39f4c80
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 11 deletions.
45 changes: 36 additions & 9 deletions handler/src/main/java/io/netty/handler/ssl/PemReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand Down Expand Up @@ -56,11 +57,25 @@ final class PemReader {
Pattern.CASE_INSENSITIVE);

static ByteBuf[] readCertificates(File file) throws CertificateException {
try {
InputStream in = new FileInputStream(file);

try {
return readCertificates(in);
} finally {
safeClose(in);
}
} catch (FileNotFoundException e) {
throw new CertificateException("could not find certificate file: " + file);
}
}

static ByteBuf[] readCertificates(InputStream in) throws CertificateException {
String content;
try {
content = readContent(file);
content = readContent(in);
} catch (IOException e) {
throw new CertificateException("failed to read a file: " + file, e);
throw new CertificateException("failed to read certificate input stream", e);
}

List<ByteBuf> certs = new ArrayList<ByteBuf>();
Expand All @@ -80,23 +95,37 @@ static ByteBuf[] readCertificates(File file) throws CertificateException {
}

if (certs.isEmpty()) {
throw new CertificateException("found no certificates: " + file);
throw new CertificateException("found no certificates in input stream");
}

return certs.toArray(new ByteBuf[certs.size()]);
}

static ByteBuf readPrivateKey(File file) throws KeyException {
try {
InputStream in = new FileInputStream(file);

try {
return readPrivateKey(in);
} finally {
safeClose(in);
}
} catch (FileNotFoundException e) {
throw new KeyException("could not fine key file: " + file);
}
}

static ByteBuf readPrivateKey(InputStream in) throws KeyException {
String content;
try {
content = readContent(file);
content = readContent(in);
} catch (IOException e) {
throw new KeyException("failed to read a file: " + file, e);
throw new KeyException("failed to read key input stream", e);
}

Matcher m = KEY_PATTERN.matcher(content);
if (!m.find()) {
throw new KeyException("found no private key: " + file);
throw new KeyException("found no private key in input stream");
}

ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
Expand All @@ -105,8 +134,7 @@ static ByteBuf readPrivateKey(File file) throws KeyException {
return der;
}

private static String readContent(File file) throws IOException {
InputStream in = new FileInputStream(file);
private static String readContent(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
byte[] buf = new byte[8192];
Expand All @@ -119,7 +147,6 @@ private static String readContent(File file) throws IOException {
}
return out.toString(CharsetUtil.US_ASCII.name());
} finally {
safeClose(in);
safeClose(out);
}
}
Expand Down
31 changes: 29 additions & 2 deletions handler/src/main/java/io/netty/handler/ssl/SslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException;
Expand Down Expand Up @@ -909,7 +910,23 @@ static PrivateKey toPrivateKey(File keyFile, String keyPassword) throws NoSuchAl
if (keyFile == null) {
return null;
}
ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
return getPrivateKeyFromByteBuffer(PemReader.readPrivateKey(keyFile), keyPassword);
}

static PrivateKey toPrivateKey(InputStream keyInputStream, String keyPassword) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException,
KeyException, IOException {
if (keyInputStream == null) {
return null;
}
return getPrivateKeyFromByteBuffer(PemReader.readPrivateKey(keyInputStream), keyPassword);
}

private static PrivateKey getPrivateKeyFromByteBuffer(ByteBuf encodedKeyBuf, String keyPassword)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException, KeyException, IOException {

byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release();

Expand Down Expand Up @@ -952,8 +969,18 @@ static X509Certificate[] toX509Certificates(File file) throws CertificateExcepti
if (file == null) {
return null;
}
return getCertificatesFromBuffers(PemReader.readCertificates(file));
}

static X509Certificate[] toX509Certificates(InputStream in) throws CertificateException {
if (in == null) {
return null;
}
return getCertificatesFromBuffers(PemReader.readCertificates(in));
}

private static X509Certificate[] getCertificatesFromBuffers(ByteBuf[] certs) throws CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteBuf[] certs = PemReader.readCertificates(file);
X509Certificate[] x509Certs = new X509Certificate[certs.length];

try {
Expand Down
75 changes: 75 additions & 0 deletions handler/src/main/java/io/netty/handler/ssl/SslContextBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

Expand All @@ -48,6 +49,17 @@ public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile);
}

/**
* Creates a builder for new server-side {@link SslContext}.
*
* @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
* @param keyFile an input stream for a PKCS#8 private key in PEM format
* @see #keyManager(InputStream, InputStream)
*/
public static SslContextBuilder forServer(InputStream keyCertChainInputStream, InputStream keyInputStream) {
return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream);
}

/**
* Creates a builder for new server-side {@link SslContext}.
*
Expand All @@ -73,6 +85,20 @@ public static SslContextBuilder forServer(
return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword);
}

/**
* Creates a builder for new server-side {@link SslContext}.
*
* @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
* @param keyInputStream an input stream for a PKCS#8 private key in PEM format
* @param keyPassword the password of the {@code keyFile}, or {@code null} if it's not
* password-protected
* @see #keyManager(InputStream, InputStream, String)
*/
public static SslContextBuilder forServer(
InputStream keyCertChainInputStream, InputStream keyInputStream, String keyPassword) {
return new SslContextBuilder(true).keyManager(keyCertChainInputStream, keyInputStream, keyPassword);
}

/**
* Creates a builder for new server-side {@link SslContext}.
*
Expand Down Expand Up @@ -136,6 +162,18 @@ public SslContextBuilder trustManager(File trustCertChainFile) {
}
}

/**
* Trusted certificates for verifying the remote endpoint's certificate. The input stream should
* contain an X.509 certificate chain in PEM format. {@code null} uses the system default.
*/
public SslContextBuilder trustManager(InputStream trustCertChainInputStream) {
try {
return trustManager(SslContext.toX509Certificates(trustCertChainInputStream));
} catch (Exception e) {
throw new IllegalArgumentException("Input stream does not contain valid certificates.", e);
}
}

/**
* Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
*/
Expand Down Expand Up @@ -167,6 +205,17 @@ public SslContextBuilder keyManager(File keyCertChainFile, File keyFile) {
return keyManager(keyCertChainFile, keyFile, null);
}

/**
* Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
* be {@code null} for client contexts, which disables mutual authentication.
*
* @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
* @param keyInputStream an input stream for a PKCS#8 private key in PEM format
*/
public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream) {
return keyManager(keyCertChainInputStream, keyInputStream, null);
}

/**
* Identifying certificate for this host. {@code keyCertChain} and {@code key} may
* be {@code null} for client contexts, which disables mutual authentication.
Expand Down Expand Up @@ -203,6 +252,32 @@ public SslContextBuilder keyManager(File keyCertChainFile, File keyFile, String
return keyManager(key, keyPassword, keyCertChain);
}

/**
* Identifying certificate for this host. {@code keyCertChainInputStream} and {@code keyInputStream} may
* be {@code null} for client contexts, which disables mutual authentication.
*
* @param keyCertChainInputStream an input stream for an X.509 certificate chain in PEM format
* @param keyInputStream an input stream for a PKCS#8 private key in PEM format
* @param keyPassword the password of the {@code keyInputStream}, or {@code null} if it's not
* password-protected
*/
public SslContextBuilder keyManager(InputStream keyCertChainInputStream, InputStream keyInputStream,
String keyPassword) {
X509Certificate[] keyCertChain;
PrivateKey key;
try {
keyCertChain = SslContext.toX509Certificates(keyCertChainInputStream);
} catch (Exception e) {
throw new IllegalArgumentException("Input stream not contain valid certificates.", e);
}
try {
key = SslContext.toPrivateKey(keyInputStream, keyPassword);
} catch (Exception e) {
throw new IllegalArgumentException("Input stream does not contain valid private key.", e);
}
return keyManager(key, keyPassword, keyCertChain);
}

/**
* Identifying certificate for this host. {@code keyCertChain} and {@code key} may
* be {@code null} for client contexts, which disables mutual authentication.
Expand Down

0 comments on commit 39f4c80

Please sign in to comment.