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:
parent
121f963b2f
commit
1d854cd967
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user