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
0f91ad841d
commit
61f812ea2a
@ -26,6 +26,7 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
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;
|
||||
@ -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>();
|
||||
@ -80,23 +95,37 @@ final class PemReader {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -105,8 +134,7 @@ final class PemReader {
|
||||
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];
|
||||
@ -119,7 +147,6 @@ final class PemReader {
|
||||
}
|
||||
return out.toString(CharsetUtil.US_ASCII.name());
|
||||
} finally {
|
||||
safeClose(in);
|
||||
safeClose(out);
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
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;
|
||||
@ -912,7 +913,23 @@ public abstract class SslContext {
|
||||
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();
|
||||
|
||||
@ -955,8 +972,18 @@ public abstract class SslContext {
|
||||
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 {
|
||||
|
@ -22,6 +22,7 @@ import javax.net.ssl.KeyManagerFactory;
|
||||
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;
|
||||
|
||||
@ -48,6 +49,17 @@ public final class SslContextBuilder {
|
||||
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}.
|
||||
*
|
||||
@ -73,6 +85,20 @@ public final class SslContextBuilder {
|
||||
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}.
|
||||
*
|
||||
@ -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.
|
||||
*/
|
||||
@ -167,6 +205,17 @@ public final class SslContextBuilder {
|
||||
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.
|
||||
@ -203,6 +252,32 @@ public final class SslContextBuilder {
|
||||
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.
|
||||
|
Loading…
Reference in New Issue
Block a user