Allow InputStreams for key/trust managers in SslContextBuilder

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`.
This commit is contained in:
Jon Chambers 2016-02-03 10:45:56 -05:00 committed by Scott Mitchell
parent 121f963b2f
commit 1d854cd967
3 changed files with 140 additions and 11 deletions

View File

@ -26,6 +26,7 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -56,11 +57,25 @@ final class PemReader {
Pattern.CASE_INSENSITIVE); Pattern.CASE_INSENSITIVE);
static ByteBuf[] readCertificates(File file) throws CertificateException { 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; String content;
try { try {
content = readContent(file); content = readContent(in);
} catch (IOException e) { } 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>(); List<ByteBuf> certs = new ArrayList<ByteBuf>();
@ -80,23 +95,37 @@ final class PemReader {
} }
if (certs.isEmpty()) { 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()]); return certs.toArray(new ByteBuf[certs.size()]);
} }
static ByteBuf readPrivateKey(File file) throws KeyException { 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; String content;
try { try {
content = readContent(file); content = readContent(in);
} catch (IOException e) { } 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); Matcher m = KEY_PATTERN.matcher(content);
if (!m.find()) { 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); ByteBuf base64 = Unpooled.copiedBuffer(m.group(1), CharsetUtil.US_ASCII);
@ -105,8 +134,7 @@ final class PemReader {
return der; return der;
} }
private static String readContent(File file) throws IOException { private static String readContent(InputStream in) throws IOException {
InputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
try { try {
byte[] buf = new byte[8192]; byte[] buf = new byte[8192];
@ -119,7 +147,6 @@ final class PemReader {
} }
return out.toString(CharsetUtil.US_ASCII.name()); return out.toString(CharsetUtil.US_ASCII.name());
} finally { } finally {
safeClose(in);
safeClose(out); safeClose(out);
} }
} }

View File

@ -42,6 +42,7 @@ import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyException; import java.security.KeyException;
@ -909,7 +910,23 @@ public abstract class SslContext {
if (keyFile == null) { if (keyFile == null) {
return 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()]; byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release(); encodedKeyBuf.readBytes(encodedKey).release();
@ -952,8 +969,18 @@ public abstract class SslContext {
if (file == null) { if (file == null) {
return 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"); CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteBuf[] certs = PemReader.readCertificates(file);
X509Certificate[] x509Certs = new X509Certificate[certs.length]; X509Certificate[] x509Certs = new X509Certificate[certs.length];
try { try {

View File

@ -22,6 +22,7 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -48,6 +49,17 @@ public final class SslContextBuilder {
return new SslContextBuilder(true).keyManager(keyCertChainFile, 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}. * Creates a builder for new server-side {@link SslContext}.
* *
@ -73,6 +85,20 @@ public final class SslContextBuilder {
return new SslContextBuilder(true).keyManager(keyCertChainFile, keyFile, keyPassword); 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}. * Creates a builder for new server-side {@link SslContext}.
* *
@ -136,6 +162,18 @@ public final class SslContextBuilder {
} }
} }
/**
* 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. * Trusted certificates for verifying the remote endpoint's certificate, {@code null} uses the system default.
*/ */
@ -167,6 +205,17 @@ public final class SslContextBuilder {
return keyManager(keyCertChainFile, keyFile, null); 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 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
* be {@code null} for client contexts, which disables mutual authentication. * be {@code null} for client contexts, which disables mutual authentication.
@ -203,6 +252,32 @@ public final class SslContextBuilder {
return keyManager(key, keyPassword, keyCertChain); 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 * Identifying certificate for this host. {@code keyCertChain} and {@code key} may
* be {@code null} for client contexts, which disables mutual authentication. * be {@code null} for client contexts, which disables mutual authentication.