Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 17 additions & 45 deletions jooby/src/main/java/io/jooby/internal/x509/JdkSslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public abstract class JdkSslContext extends SslContext {

static {
SSLContext context;
int i;
try {
context = SSLContext.getInstance(PROTOCOL);
context.init(null, null, null);
Expand All @@ -63,12 +62,11 @@ public abstract class JdkSslContext extends SslContext {

// Choose the sensible default list of protocols.
final String[] supportedProtocols = engine.getSupportedProtocols();
Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
for (i = 0; i < supportedProtocols.length; ++i) {
supportedProtocolsSet.add(supportedProtocols[i]);
}
List<String> protocols = new ArrayList<String>();
addIfSupported(supportedProtocolsSet, protocols, "TLSv1.2", "TLSv1.1", "TLSv1");
Set<String> supportedProtocolsSet = new HashSet<>(Arrays.asList(supportedProtocols));
List<String> protocols = new ArrayList<>();

// Modernized for Java 21: prioritize TLS 1.3 and TLS 1.2
addIfSupported(supportedProtocolsSet, protocols, "TLSv1.3", "TLSv1.2");

if (!protocols.isEmpty()) {
PROTOCOLS = protocols.toArray(new String[0]);
Expand All @@ -78,26 +76,26 @@ public abstract class JdkSslContext extends SslContext {

// Choose the sensible default list of cipher suites.
final String[] supportedCiphers = engine.getSupportedCipherSuites();
SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
for (i = 0; i < supportedCiphers.length; ++i) {
SUPPORTED_CIPHERS.add(supportedCiphers[i]);
}
List<String> ciphers = new ArrayList<String>();
SUPPORTED_CIPHERS = new HashSet<>(Arrays.asList(supportedCiphers));
List<String> ciphers = new ArrayList<>();

addIfSupported(
SUPPORTED_CIPHERS,
ciphers,
// XXX: Make sure to sync this list with OpenSslEngineFactory.
// GCM (Galois/Counter Mode) requires JDK 8.
// TLS 1.3 Ciphers
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
// Modern TLS 1.2 Ciphers
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
// AES256 requires JCE unlimited strength jurisdiction policy files.
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
// GCM (Galois/Counter Mode) requires JDK 8.
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
// AES256 requires JCE unlimited strength jurisdiction policy files.
"TLS_RSA_WITH_AES_256_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA");
"TLS_RSA_WITH_AES_256_CBC_SHA");

if (ciphers.isEmpty()) {
// Use the default from JDK as fallback.
Expand Down Expand Up @@ -125,7 +123,6 @@ private static void addIfSupported(
}
}

/** Returns the JDK {@link SSLSessionContext} object held by this context. */
@Override
public final SSLSessionContext sessionContext() {
return context().getServerSessionContext();
Expand All @@ -141,18 +138,6 @@ public final long sessionTimeout() {
return sessionContext().getSessionTimeout();
}

/**
* Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate
* chain.
*
* @param certChainFile a X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}. {@code null} if it's not
* password-protected.
* @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
* @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate
* chain.
*/
protected static KeyManagerFactory buildKeyManagerFactory(
final InputStream certChainFile, final InputStream keyFile, final String keyPassword)
throws UnrecoverableKeyException,
Expand All @@ -171,19 +156,6 @@ protected static KeyManagerFactory buildKeyManagerFactory(
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword);
}

/**
* Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, and
* a certificate chain.
*
* @param certChainFile a X.509 certificate chain file in PEM format
* @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket
* Extension Reference Guide for information about standard algorithm names.
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}. {@code null} if it's not
* password-protected.
* @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
* and a certificate chain.
*/
protected static KeyManagerFactory buildKeyManagerFactory(
final InputStream certChainFile,
final String keyAlgorithm,
Expand Down
11 changes: 5 additions & 6 deletions jooby/src/main/java/io/jooby/internal/x509/PemReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Base64;
Expand All @@ -21,8 +20,7 @@
import io.jooby.internal.IOUtils;

/**
* Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link
* KeyStore} easily.
* Reads a PEM file and converts it into a list of DERs.
*
* <p>Borrowed from <a href="http://netty.io">Netty</a>
*/
Expand All @@ -36,6 +34,7 @@ final class PemReader {
+ // Base64 text
"-+END\\s+.*CERTIFICATE[^-]*-+", // Footer
Pattern.CASE_INSENSITIVE);

private static final Pattern KEY_PATTERN =
Pattern.compile(
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"
Expand All @@ -49,13 +48,12 @@ static List<ByteBuffer> readCertificates(final InputStream file)
throws CertificateException, IOException {
String content = IOUtils.toString(file, StandardCharsets.UTF_8);

List<ByteBuffer> certs = new ArrayList<ByteBuffer>();
List<ByteBuffer> certs = new ArrayList<>();
Matcher m = CERT_PATTERN.matcher(content);
int start = 0;
while (m.find(start)) {
ByteBuffer buffer = ByteBuffer.wrap(decode(m.group(1)));
certs.add(buffer);

start = m.end();
}

Expand All @@ -67,7 +65,8 @@ static List<ByteBuffer> readCertificates(final InputStream file)
}

private static byte[] decode(String value) {
return Base64.getDecoder().decode(value.replaceAll("(?:\\r\\n|\\n\\r|\\n|\\r)", ""));
// MimeDecoder automatically strips out \r and \n characters
return Base64.getMimeDecoder().decode(value);
}

static ByteBuffer readPrivateKey(final InputStream file) throws KeyException, IOException {
Expand Down
66 changes: 4 additions & 62 deletions jooby/src/main/java/io/jooby/internal/x509/SslContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package io.jooby.internal.x509;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -45,27 +44,7 @@
* SslHandler}. Internally, it is implemented via JDK's {@link SSLContext} or OpenSSL's {@code
* SSL_CTX}.
*
* <h3>Making your server support SSL/TLS</h3>
*
* <pre>
* // In your {@link ChannelInitializer}:
* {@link ChannelPipeline} p = channel.pipeline();
* {@link SslContext} sslCtx = {@link SslContextBuilder#forServer(File, File) SslContextBuilder.forServer(...)}.build();
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator) sslCtx.newEngine(channel.alloc())});
* ...
* </pre>
*
* <h3>Making your client support SSL/TLS</h3>
*
* <pre>
* // In your {@link ChannelInitializer}:
* {@link ChannelPipeline} p = channel.pipeline();
* {@link SslContext} sslCtx = {@link SslContextBuilder#forClient() SslContextBuilder.forClient()}.build();
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator, String, int) sslCtx.newEngine(channel.alloc(), host, port)});
* ...
* </pre>
*
* Borrowed from <a href="http://netty.io">Netty</a>
* <p>Borrowed from <a href="http://netty.io">Netty</a>
*/
public abstract class SslContext {
static final CertificateFactory X509_CERT_FACTORY;
Expand Down Expand Up @@ -97,33 +76,14 @@ public static SslContext newServerContextInternal(
sessionTimeout);
}

/** Returns the size of the cache used for storing SSL session objects. */
public abstract long sessionCacheSize();

public abstract long sessionTimeout();

public abstract SSLContext context();

/** Returns the {@link SSLSessionContext} object held by this context. */
public abstract SSLSessionContext sessionContext();

/**
* Generates a key specification for an (encrypted) private key.
*
* @param password characters, if {@code null} or empty an unencrypted key is assumed
* @param key bytes of the DER encoded private key
* @return a key specification
* @throws IOException if parsing {@code key} fails
* @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
* @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is
* unkown
* @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be
* generated
* @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to
* decrypt {@code key}
* @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow
* faulty
*/
protected static PKCS8EncodedKeySpec generateKeySpec(final char[] password, final byte[] key)
throws IOException,
NoSuchAlgorithmException,
Expand All @@ -148,15 +108,6 @@ protected static PKCS8EncodedKeySpec generateKeySpec(final char[] password, fina
return encryptedPrivateKeyInfo.getKeySpec(cipher);
}

/**
* Generates a new {@link KeyStore}.
*
* @param certChainFile a X.509 certificate chain file in PEM format,
* @param keyFile a PKCS#8 private key file in PEM format,
* @param keyPasswordChars the password of the {@code keyFile}. {@code null} if it's not
* password-protected.
* @return generated {@link KeyStore}.
*/
static KeyStore buildKeyStore(
final InputStream certChainFile, final InputStream keyFile, final char[] keyPasswordChars)
throws KeyStoreException,
Expand Down Expand Up @@ -189,30 +140,22 @@ static KeyStore buildKeyStore(

CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<ByteBuffer> certs = PemReader.readCertificates(certChainFile);
List<Certificate> certChain = new ArrayList<Certificate>(certs.size());
List<Certificate> certChain = new ArrayList<>(certs.size());

for (ByteBuffer buf : certs) {
certChain.add(cf.generateCertificate(new ByteArrayInputStream(buf.array())));
}

KeyStore ks = KeyStore.getInstance("JKS");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[0]));
return ks;
}

/**
* Build a {@link TrustManagerFactory} from a certificate chain file.
*
* @param certChainFile The certificate file to build from.
* @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not
* {@code null}.
* @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile}
*/
protected static TrustManagerFactory buildTrustManagerFactory(
final InputStream certChainFile, TrustManagerFactory trustManagerFactory)
throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");

Expand All @@ -225,7 +168,6 @@ protected static TrustManagerFactory buildTrustManagerFactory(
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
}

// Set up trust manager factory to use our key store.
if (trustManagerFactory == null) {
trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
Expand Down
Loading