Skip to content

Commit e7bc635

Browse files
committed
The internal SSL utility classes (SslContext, JdkSslContext, PemReader) currently rely on legacy defaults and need to be modernized to fully leverage Java 21 standards.
These classes are essential for zero-dependency PEM parsing and SSL configuration across supported servers, but they currently default to the outdated `JKS` keystore format, prioritize older TLS versions, and utilize legacy cipher suites. * **Prioritize TLS 1.3:** Update the protocol fallback logic in `JdkSslContext` to prioritize `TLSv1.3` and drop deprecated protocols. * **Modernize Ciphers:** Remove outdated ciphers (e.g., 3DES) and ensure high-security TLS 1.3 ciphers are at the top of the preference list. * **Update Keystore Format:** Replace hardcoded `"JKS"` instances with `KeyStore.getDefaultType()` to utilize the modern PKCS12 standard. * **Optimize PEM Parsing:** Refactor `PemReader` to use `Base64.getMimeDecoder()` for native, efficient handling of line breaks, replacing the manual regex string manipulation. * **Syntax Cleanup:** Apply the diamond operator (`<>`) across all files to clean up legacy generics. fix #3877
1 parent eb18029 commit e7bc635

File tree

3 files changed

+26
-113
lines changed

3 files changed

+26
-113
lines changed

jooby/src/main/java/io/jooby/internal/x509/JdkSslContext.java

Lines changed: 17 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public abstract class JdkSslContext extends SslContext {
5151

5252
static {
5353
SSLContext context;
54-
int i;
5554
try {
5655
context = SSLContext.getInstance(PROTOCOL);
5756
context.init(null, null, null);
@@ -63,12 +62,11 @@ public abstract class JdkSslContext extends SslContext {
6362

6463
// Choose the sensible default list of protocols.
6564
final String[] supportedProtocols = engine.getSupportedProtocols();
66-
Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
67-
for (i = 0; i < supportedProtocols.length; ++i) {
68-
supportedProtocolsSet.add(supportedProtocols[i]);
69-
}
70-
List<String> protocols = new ArrayList<String>();
71-
addIfSupported(supportedProtocolsSet, protocols, "TLSv1.2", "TLSv1.1", "TLSv1");
65+
Set<String> supportedProtocolsSet = new HashSet<>(Arrays.asList(supportedProtocols));
66+
List<String> protocols = new ArrayList<>();
67+
68+
// Modernized for Java 21: prioritize TLS 1.3 and TLS 1.2
69+
addIfSupported(supportedProtocolsSet, protocols, "TLSv1.3", "TLSv1.2");
7270

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

7977
// Choose the sensible default list of cipher suites.
8078
final String[] supportedCiphers = engine.getSupportedCipherSuites();
81-
SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
82-
for (i = 0; i < supportedCiphers.length; ++i) {
83-
SUPPORTED_CIPHERS.add(supportedCiphers[i]);
84-
}
85-
List<String> ciphers = new ArrayList<String>();
79+
SUPPORTED_CIPHERS = new HashSet<>(Arrays.asList(supportedCiphers));
80+
List<String> ciphers = new ArrayList<>();
81+
8682
addIfSupported(
8783
SUPPORTED_CIPHERS,
8884
ciphers,
89-
// XXX: Make sure to sync this list with OpenSslEngineFactory.
90-
// GCM (Galois/Counter Mode) requires JDK 8.
85+
// TLS 1.3 Ciphers
86+
"TLS_AES_256_GCM_SHA384",
87+
"TLS_AES_128_GCM_SHA256",
88+
"TLS_CHACHA20_POLY1305_SHA256",
89+
// Modern TLS 1.2 Ciphers
90+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
91+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
92+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
9193
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
9294
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
93-
// AES256 requires JCE unlimited strength jurisdiction policy files.
9495
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
95-
// GCM (Galois/Counter Mode) requires JDK 8.
9696
"TLS_RSA_WITH_AES_128_GCM_SHA256",
9797
"TLS_RSA_WITH_AES_128_CBC_SHA",
98-
// AES256 requires JCE unlimited strength jurisdiction policy files.
99-
"TLS_RSA_WITH_AES_256_CBC_SHA",
100-
"SSL_RSA_WITH_3DES_EDE_CBC_SHA");
98+
"TLS_RSA_WITH_AES_256_CBC_SHA");
10199

102100
if (ciphers.isEmpty()) {
103101
// Use the default from JDK as fallback.
@@ -125,7 +123,6 @@ private static void addIfSupported(
125123
}
126124
}
127125

128-
/** Returns the JDK {@link SSLSessionContext} object held by this context. */
129126
@Override
130127
public final SSLSessionContext sessionContext() {
131128
return context().getServerSessionContext();
@@ -141,18 +138,6 @@ public final long sessionTimeout() {
141138
return sessionContext().getSessionTimeout();
142139
}
143140

144-
/**
145-
* Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate
146-
* chain.
147-
*
148-
* @param certChainFile a X.509 certificate chain file in PEM format
149-
* @param keyFile a PKCS#8 private key file in PEM format
150-
* @param keyPassword the password of the {@code keyFile}. {@code null} if it's not
151-
* password-protected.
152-
* @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
153-
* @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate
154-
* chain.
155-
*/
156141
protected static KeyManagerFactory buildKeyManagerFactory(
157142
final InputStream certChainFile, final InputStream keyFile, final String keyPassword)
158143
throws UnrecoverableKeyException,
@@ -171,19 +156,6 @@ protected static KeyManagerFactory buildKeyManagerFactory(
171156
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword);
172157
}
173158

174-
/**
175-
* Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, and
176-
* a certificate chain.
177-
*
178-
* @param certChainFile a X.509 certificate chain file in PEM format
179-
* @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket
180-
* Extension Reference Guide for information about standard algorithm names.
181-
* @param keyFile a PKCS#8 private key file in PEM format
182-
* @param keyPassword the password of the {@code keyFile}. {@code null} if it's not
183-
* password-protected.
184-
* @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
185-
* and a certificate chain.
186-
*/
187159
protected static KeyManagerFactory buildKeyManagerFactory(
188160
final InputStream certChainFile,
189161
final String keyAlgorithm,

jooby/src/main/java/io/jooby/internal/x509/PemReader.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.nio.ByteBuffer;
1111
import java.nio.charset.StandardCharsets;
1212
import java.security.KeyException;
13-
import java.security.KeyStore;
1413
import java.security.cert.CertificateException;
1514
import java.util.ArrayList;
1615
import java.util.Base64;
@@ -21,8 +20,7 @@
2120
import io.jooby.internal.IOUtils;
2221

2322
/**
24-
* Reads a PEM file and converts it into a list of DERs so that they are imported into a {@link
25-
* KeyStore} easily.
23+
* Reads a PEM file and converts it into a list of DERs.
2624
*
2725
* <p>Borrowed from <a href="http://netty.io">Netty</a>
2826
*/
@@ -36,6 +34,7 @@ final class PemReader {
3634
+ // Base64 text
3735
"-+END\\s+.*CERTIFICATE[^-]*-+", // Footer
3836
Pattern.CASE_INSENSITIVE);
37+
3938
private static final Pattern KEY_PATTERN =
4039
Pattern.compile(
4140
"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+"
@@ -49,13 +48,12 @@ static List<ByteBuffer> readCertificates(final InputStream file)
4948
throws CertificateException, IOException {
5049
String content = IOUtils.toString(file, StandardCharsets.UTF_8);
5150

52-
List<ByteBuffer> certs = new ArrayList<ByteBuffer>();
51+
List<ByteBuffer> certs = new ArrayList<>();
5352
Matcher m = CERT_PATTERN.matcher(content);
5453
int start = 0;
5554
while (m.find(start)) {
5655
ByteBuffer buffer = ByteBuffer.wrap(decode(m.group(1)));
5756
certs.add(buffer);
58-
5957
start = m.end();
6058
}
6159

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

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

7372
static ByteBuffer readPrivateKey(final InputStream file) throws KeyException, IOException {

jooby/src/main/java/io/jooby/internal/x509/SslContext.java

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package io.jooby.internal.x509;
77

88
import java.io.ByteArrayInputStream;
9-
import java.io.File;
109
import java.io.IOException;
1110
import java.io.InputStream;
1211
import java.nio.ByteBuffer;
@@ -45,27 +44,7 @@
4544
* SslHandler}. Internally, it is implemented via JDK's {@link SSLContext} or OpenSSL's {@code
4645
* SSL_CTX}.
4746
*
48-
* <h3>Making your server support SSL/TLS</h3>
49-
*
50-
* <pre>
51-
* // In your {@link ChannelInitializer}:
52-
* {@link ChannelPipeline} p = channel.pipeline();
53-
* {@link SslContext} sslCtx = {@link SslContextBuilder#forServer(File, File) SslContextBuilder.forServer(...)}.build();
54-
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator) sslCtx.newEngine(channel.alloc())});
55-
* ...
56-
* </pre>
57-
*
58-
* <h3>Making your client support SSL/TLS</h3>
59-
*
60-
* <pre>
61-
* // In your {@link ChannelInitializer}:
62-
* {@link ChannelPipeline} p = channel.pipeline();
63-
* {@link SslContext} sslCtx = {@link SslContextBuilder#forClient() SslContextBuilder.forClient()}.build();
64-
* p.addLast("ssl", {@link #newEngine(ByteBufAllocator, String, int) sslCtx.newEngine(channel.alloc(), host, port)});
65-
* ...
66-
* </pre>
67-
*
68-
* Borrowed from <a href="http://netty.io">Netty</a>
47+
* <p>Borrowed from <a href="http://netty.io">Netty</a>
6948
*/
7049
public abstract class SslContext {
7150
static final CertificateFactory X509_CERT_FACTORY;
@@ -97,33 +76,14 @@ public static SslContext newServerContextInternal(
9776
sessionTimeout);
9877
}
9978

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

10381
public abstract long sessionTimeout();
10482

10583
public abstract SSLContext context();
10684

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

110-
/**
111-
* Generates a key specification for an (encrypted) private key.
112-
*
113-
* @param password characters, if {@code null} or empty an unencrypted key is assumed
114-
* @param key bytes of the DER encoded private key
115-
* @return a key specification
116-
* @throws IOException if parsing {@code key} fails
117-
* @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
118-
* @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is
119-
* unkown
120-
* @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be
121-
* generated
122-
* @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to
123-
* decrypt {@code key}
124-
* @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow
125-
* faulty
126-
*/
12787
protected static PKCS8EncodedKeySpec generateKeySpec(final char[] password, final byte[] key)
12888
throws IOException,
12989
NoSuchAlgorithmException,
@@ -148,15 +108,6 @@ protected static PKCS8EncodedKeySpec generateKeySpec(final char[] password, fina
148108
return encryptedPrivateKeyInfo.getKeySpec(cipher);
149109
}
150110

151-
/**
152-
* Generates a new {@link KeyStore}.
153-
*
154-
* @param certChainFile a X.509 certificate chain file in PEM format,
155-
* @param keyFile a PKCS#8 private key file in PEM format,
156-
* @param keyPasswordChars the password of the {@code keyFile}. {@code null} if it's not
157-
* password-protected.
158-
* @return generated {@link KeyStore}.
159-
*/
160111
static KeyStore buildKeyStore(
161112
final InputStream certChainFile, final InputStream keyFile, final char[] keyPasswordChars)
162113
throws KeyStoreException,
@@ -189,30 +140,22 @@ static KeyStore buildKeyStore(
189140

190141
CertificateFactory cf = CertificateFactory.getInstance("X.509");
191142
List<ByteBuffer> certs = PemReader.readCertificates(certChainFile);
192-
List<Certificate> certChain = new ArrayList<Certificate>(certs.size());
143+
List<Certificate> certChain = new ArrayList<>(certs.size());
193144

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

198-
KeyStore ks = KeyStore.getInstance("JKS");
149+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
199150
ks.load(null, null);
200151
ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[0]));
201152
return ks;
202153
}
203154

204-
/**
205-
* Build a {@link TrustManagerFactory} from a certificate chain file.
206-
*
207-
* @param certChainFile The certificate file to build from.
208-
* @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not
209-
* {@code null}.
210-
* @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile}
211-
*/
212155
protected static TrustManagerFactory buildTrustManagerFactory(
213156
final InputStream certChainFile, TrustManagerFactory trustManagerFactory)
214157
throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
215-
KeyStore ks = KeyStore.getInstance("JKS");
158+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
216159
ks.load(null, null);
217160
CertificateFactory cf = CertificateFactory.getInstance("X.509");
218161

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

228-
// Set up trust manager factory to use our key store.
229171
if (trustManagerFactory == null) {
230172
trustManagerFactory =
231173
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

0 commit comments

Comments
 (0)