Skip to content

Commit 076d333

Browse files
authored
Merge pull request #3878 from jooby-project/3877
The internal SSL utility classes (`SslContext`, `JdkSslContext`, `Pem…
2 parents eb18029 + e7bc635 commit 076d333

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)