Skip to content

Commit 4145ced

Browse files
committed
refactor: Address Sonar findings on PemSslContext
Extracts the nested try in decodeCertificateChain into a helper, chains the RSA InvalidKeySpecException as suppressed when the EC fallback also fails, drops the unused PrivateKey parameter on requireMinimumStrength, names the 2048/256-bit floors and the 8-byte signature probe as constants, and removes redundant `throws Exception` declarations on tests that don't throw.
1 parent d1dceb7 commit 4145ced

2 files changed

Lines changed: 48 additions & 28 deletions

File tree

src/main/java/com/retailsvc/http/internal/PemSslContext.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
/** Loads a {@link SSLContext} from a PEM certificate chain and PEM PKCS#8 private key. */
2626
public 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
}

src/test/java/com/retailsvc/http/internal/PemSslContextTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class PemSslContextTest {
2121
private static final Path WEAK_RSA_KEY = Path.of("src/test/resources/tls/weak-rsa-key.pem");
2222

2323
@Test
24-
void loadsRsaPemPair() throws Exception {
24+
void loadsRsaPemPair() {
2525
SSLContext context = PemSslContext.load(RSA_CERT, RSA_KEY);
2626

2727
assertThat(context).isNotNull();
@@ -30,7 +30,7 @@ void loadsRsaPemPair() throws Exception {
3030
}
3131

3232
@Test
33-
void loadsEcPemPair() throws Exception {
33+
void loadsEcPemPair() {
3434
SSLContext context = PemSslContext.load(EC_CERT, EC_KEY);
3535

3636
assertThat(context).isNotNull();
@@ -88,7 +88,7 @@ void rejectsEmptyCertificateChain() throws Exception {
8888
}
8989

9090
@Test
91-
void acceptsEcKeyAtMinimumStrength() throws Exception {
91+
void acceptsEcKeyAtMinimumStrength() {
9292
// P-256 (256 bits) is exactly at the floor — must pass.
9393
SSLContext ctx = PemSslContext.load(EC_CERT, EC_KEY);
9494
assertThat(ctx).isNotNull();

0 commit comments

Comments
 (0)