2525/** Loads a {@link SSLContext} from a PEM certificate chain and PEM PKCS#8 private key. */
2626public final class PemSslContext {
2727
28+ private static final int MIN_RSA_KEY_BITS = 2048 ;
29+ private static final int MIN_EC_KEY_BITS = 256 ;
30+ private static final byte [] SIGNATURE_PROBE = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
31+
2832 private PemSslContext () {}
2933
3034 public static SSLContext load (Path certChainPem , Path privateKeyPem ) {
@@ -46,19 +50,7 @@ private static Certificate[] readCertificateChain(Path path) {
4650 // JEP 524 swap point: replace this body with PEMDecoder when the JDK PEM API lands.
4751 private static Certificate [] decodeCertificateChain (byte [] pem , Path source ) {
4852 try {
49- CertificateFactory factory = CertificateFactory .getInstance ("X.509" );
50- Collection <? extends Certificate > certs ;
51- try {
52- certs = factory .generateCertificates (new ByteArrayInputStream (pem ));
53- } catch (CertificateException e ) {
54- // JDK X509Factory throws "No certificate data found" for input with no
55- // BEGIN CERTIFICATE block. Treat that as an empty chain rather than a parse error.
56- if (e .getMessage () != null && e .getMessage ().contains ("No certificate data found" )) {
57- throw new IllegalStateException (
58- "No certificates found in TLS certificate chain: " + source );
59- }
60- throw e ;
61- }
53+ Collection <? extends Certificate > certs = parseCertificates (pem , source );
6254 Certificate [] chain = certs .toArray (new Certificate [0 ]);
6355 if (chain .length == 0 ) {
6456 throw new IllegalStateException (
@@ -70,6 +62,22 @@ private static Certificate[] decodeCertificateChain(byte[] pem, Path source) {
7062 }
7163 }
7264
65+ private static Collection <? extends Certificate > parseCertificates (byte [] pem , Path source )
66+ throws GeneralSecurityException {
67+ CertificateFactory factory = CertificateFactory .getInstance ("X.509" );
68+ try {
69+ return factory .generateCertificates (new ByteArrayInputStream (pem ));
70+ } catch (CertificateException e ) {
71+ // JDK X509Factory throws "No certificate data found" for input with no
72+ // BEGIN CERTIFICATE block. Treat that as an empty chain rather than a parse error.
73+ if (e .getMessage () != null && e .getMessage ().contains ("No certificate data found" )) {
74+ throw new IllegalStateException (
75+ "No certificates found in TLS certificate chain: " + source , e );
76+ }
77+ throw e ;
78+ }
79+ }
80+
7381 private static PrivateKey readPrivateKey (Path path ) {
7482 String pem ;
7583 try {
@@ -99,10 +107,15 @@ private static PrivateKey decodePrivateKey(String pem, Path source) {
99107 try {
100108 return KeyFactory .getInstance ("EC" ).generatePrivate (spec );
101109 } catch (InvalidKeySpecException ecFail ) {
102- throw new IllegalStateException (
103- "Unsupported TLS private key algorithm in " + source , ecFail );
110+ IllegalStateException failure =
111+ new IllegalStateException ("Unsupported TLS private key algorithm in " + source , ecFail );
112+ failure .addSuppressed (rsaFail );
113+ throw failure ;
104114 } catch (GeneralSecurityException e ) {
105- throw new IllegalStateException ("Failed to parse TLS private key from " + source , e );
115+ IllegalStateException failure =
116+ new IllegalStateException ("Failed to parse TLS private key from " + source , e );
117+ failure .addSuppressed (rsaFail );
118+ throw failure ;
106119 }
107120 } catch (GeneralSecurityException e ) {
108121 throw new IllegalStateException ("Failed to parse TLS private key from " + source , e );
@@ -111,7 +124,7 @@ private static PrivateKey decodePrivateKey(String pem, Path source) {
111124
112125 private static SSLContext buildSslContext (Certificate [] chain , PrivateKey key ) {
113126 verifyKeyMatchesCert (key , chain [0 ]);
114- requireMinimumStrength (key , chain [0 ]);
127+ requireMinimumStrength (chain [0 ]);
115128 try {
116129 KeyStore ks = KeyStore .getInstance ("PKCS12" );
117130 ks .load (null , null );
@@ -126,21 +139,29 @@ private static SSLContext buildSslContext(Certificate[] chain, PrivateKey key) {
126139 }
127140 }
128141
129- private static void requireMinimumStrength (PrivateKey key , Certificate cert ) {
142+ private static void requireMinimumStrength (Certificate cert ) {
130143 PublicKey publicKey = cert .getPublicKey ();
131144 switch (publicKey ) {
132145 case RSAPublicKey rsa -> {
133146 int bits = rsa .getModulus ().bitLength ();
134- if (bits < 2048 ) {
147+ if (bits < MIN_RSA_KEY_BITS ) {
135148 throw new IllegalStateException (
136- "TLS RSA key below minimum strength: " + bits + " bits (require >= 2048)" );
149+ "TLS RSA key below minimum strength: "
150+ + bits
151+ + " bits (require >= "
152+ + MIN_RSA_KEY_BITS
153+ + ")" );
137154 }
138155 }
139156 case ECPublicKey ec -> {
140157 int bits = ec .getParams ().getCurve ().getField ().getFieldSize ();
141- if (bits < 256 ) {
158+ if (bits < MIN_EC_KEY_BITS ) {
142159 throw new IllegalStateException (
143- "TLS EC key below minimum strength: " + bits + " bits (require >= 256)" );
160+ "TLS EC key below minimum strength: "
161+ + bits
162+ + " bits (require >= "
163+ + MIN_EC_KEY_BITS
164+ + ")" );
144165 }
145166 }
146167 default ->
@@ -158,20 +179,19 @@ private static void verifyKeyMatchesCert(PrivateKey key, Certificate cert) {
158179 throw new IllegalStateException (
159180 "Unsupported TLS private key algorithm: " + key .getAlgorithm ());
160181 };
161- byte [] probe = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
162182 byte [] signature ;
163183 try {
164184 Signature signer = Signature .getInstance (algorithm );
165185 signer .initSign (key );
166- signer .update (probe );
186+ signer .update (SIGNATURE_PROBE );
167187 signature = signer .sign ();
168188 } catch (GeneralSecurityException e ) {
169189 throw new IllegalStateException ("TLS certificate and private key do not match" , e );
170190 }
171191 try {
172192 Signature verifier = Signature .getInstance (algorithm );
173193 verifier .initVerify (cert .getPublicKey ());
174- verifier .update (probe );
194+ verifier .update (SIGNATURE_PROBE );
175195 if (!verifier .verify (signature )) {
176196 throw new IllegalStateException ("TLS certificate and private key do not match" );
177197 }
0 commit comments