Skip to content

Commit 37cbbe0

Browse files
Refactored SSLUtils.loadKey using PEMParser
- Removed custom DER parsing and Base64 decoding in favor of Bouncy Castle PEM Parser - Refactored SSLUtilsTest methods with reusable assertion method and algorithm checks
1 parent 7e9f9b7 commit 37cbbe0

File tree

2 files changed

+91
-213
lines changed

2 files changed

+91
-213
lines changed

util/src/main/java/io/kubernetes/client/util/SSLUtils.java

Lines changed: 28 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@
1212
*/
1313
package io.kubernetes.client.util;
1414

15-
import java.io.BufferedReader;
1615
import java.io.ByteArrayInputStream;
1716
import java.io.File;
1817
import java.io.FileInputStream;
1918
import java.io.IOException;
2019
import java.io.InputStream;
2120
import java.io.InputStreamReader;
2221
import java.io.StringWriter;
23-
import java.math.BigInteger;
24-
import java.security.KeyFactory;
22+
import java.security.KeyPair;
2523
import java.security.KeyStore;
2624
import java.security.KeyStoreException;
2725
import java.security.NoSuchAlgorithmException;
@@ -33,12 +31,10 @@
3331
import java.security.cert.CertificateFactory;
3432
import java.security.cert.X509Certificate;
3533
import java.security.spec.InvalidKeySpecException;
36-
import java.security.spec.PKCS8EncodedKeySpec;
37-
import java.security.spec.RSAPrivateCrtKeySpec;
3834
import java.util.Collection;
3935
import javax.net.ssl.KeyManager;
4036
import javax.net.ssl.KeyManagerFactory;
41-
import org.apache.commons.codec.binary.Base64;
37+
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
4238
import org.bouncycastle.openssl.PEMKeyPair;
4339
import org.bouncycastle.openssl.PEMParser;
4440
import org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator;
@@ -117,54 +113,43 @@ public static String recognizePrivateKeyAlgo(byte[] privateKeyBytes) {
117113
}
118114

119115
public static PrivateKey loadKey(byte[] privateKeyBytes)
120-
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
116+
throws IOException, InvalidKeySpecException {
121117
return loadKey(
122118
new ByteArrayInputStream(privateKeyBytes), recognizePrivateKeyAlgo(privateKeyBytes));
123119
}
124120

125121
public static PrivateKey loadKey(byte[] pemPrivateKeyBytes, String algo)
126-
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
122+
throws IOException, InvalidKeySpecException {
127123
return loadKey(new ByteArrayInputStream(pemPrivateKeyBytes), algo);
128124
}
129125

130126
public static PrivateKey loadKey(InputStream keyInputStream, String clientKeyAlgo)
131-
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
132-
133-
// Try PKCS7 / EC
134-
if (clientKeyAlgo.equals("EC")) {
135-
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
136-
PEMParser pemParser = new PEMParser(new InputStreamReader(keyInputStream));
137-
Object pemObject;
138-
while ((pemObject = pemParser.readObject()) != null) {
139-
if (pemObject instanceof PEMKeyPair) {
140-
return new JcaPEMKeyConverter().getKeyPair(((PEMKeyPair) pemObject)).getPrivate();
141-
}
127+
throws IOException, InvalidKeySpecException {
128+
final PrivateKey privateKey;
129+
try (final PEMParser pemParser = new PEMParser(new InputStreamReader(keyInputStream))) {
130+
final Object pemObject = pemParser.readObject();
131+
if (pemObject == null) {
132+
final String message = String.format("PEM Private Key Algorithm [%s] not parsed", clientKeyAlgo);
133+
throw new InvalidKeySpecException(message);
134+
}
135+
final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
136+
if (pemObject instanceof PEMKeyPair) {
137+
final PEMKeyPair pemKeyPair = (PEMKeyPair) pemObject;
138+
final KeyPair keyPair = converter.getKeyPair(pemKeyPair);
139+
privateKey = keyPair.getPrivate();
140+
} else if (pemObject instanceof PrivateKeyInfo) {
141+
final PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemObject;
142+
privateKey = converter.getPrivateKey(privateKeyInfo);
143+
} else {
144+
final String pemObjectType = pemObject.getClass().getSimpleName();
145+
final String message = String.format("PEM Private Key Algorithm [%s] Type [%s] not supported",
146+
clientKeyAlgo,
147+
pemObjectType
148+
);
149+
throw new InvalidKeySpecException(message);
142150
}
143151
}
144-
145-
byte[] keyBytes = decodePem(keyInputStream);
146-
147-
// Try PKCS1 / RSA
148-
if (clientKeyAlgo.equals("RSA")) {
149-
RSAPrivateCrtKeySpec keySpec = decodePKCS1(keyBytes);
150-
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
151-
}
152-
153-
// Try PKCS8
154-
// TODO: There _has_ to be a better way to do this, but I spent >
155-
// 2 hours trying to find it and failed...
156-
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
157-
try {
158-
return KeyFactory.getInstance("RSA").generatePrivate(spec);
159-
} catch (InvalidKeySpecException ex) {
160-
// ignore if it's not RSA
161-
}
162-
try {
163-
return KeyFactory.getInstance("ECDSA").generatePrivate(spec);
164-
} catch (InvalidKeySpecException ex) {
165-
// ignore if it's not DSA
166-
}
167-
throw new InvalidKeySpecException("Unknown type of PKCS8 Private Key, tried RSA and ECDSA");
152+
return privateKey;
168153
}
169154

170155
public static KeyStore createKeyStore(
@@ -205,140 +190,6 @@ public static KeyStore createKeyStore(
205190
return keyStore;
206191
}
207192

208-
// This method is inspired and partly taken over from
209-
// http://oauth.googlecode.com/svn/code/java/
210-
// All credits to belong to them.
211-
private static byte[] decodePem(InputStream keyInputStream) throws IOException {
212-
BufferedReader reader = new BufferedReader(new InputStreamReader(keyInputStream));
213-
try {
214-
String line;
215-
while ((line = reader.readLine()) != null) {
216-
if (line.contains("-----BEGIN ")) {
217-
return readBytes(reader, line.trim().replace("BEGIN", "END"));
218-
}
219-
}
220-
throw new IOException("PEM is invalid: no begin marker");
221-
} finally {
222-
reader.close();
223-
}
224-
}
225-
226-
private static byte[] readBytes(BufferedReader reader, String endMarker) throws IOException {
227-
String line;
228-
StringBuffer buf = new StringBuffer();
229-
230-
while ((line = reader.readLine()) != null) {
231-
if (line.indexOf(endMarker) != -1) {
232-
return Base64.decodeBase64(buf.toString());
233-
}
234-
buf.append(line.trim());
235-
}
236-
throw new IOException("PEM is invalid : No end marker");
237-
}
238-
239-
public static RSAPrivateCrtKeySpec decodePKCS1(byte[] keyBytes) throws IOException {
240-
DerParser parser = new DerParser(keyBytes);
241-
Asn1Object sequence = parser.read();
242-
sequence.validateSequence();
243-
parser = new DerParser(sequence.getValue());
244-
parser.read();
245-
246-
return new RSAPrivateCrtKeySpec(
247-
next(parser),
248-
next(parser),
249-
next(parser),
250-
next(parser),
251-
next(parser),
252-
next(parser),
253-
next(parser),
254-
next(parser));
255-
}
256-
257-
private static BigInteger next(DerParser parser) throws IOException {
258-
return parser.read().getInteger();
259-
}
260-
261-
static class DerParser {
262-
263-
private InputStream in;
264-
265-
DerParser(byte[] bytes) throws IOException {
266-
this.in = new ByteArrayInputStream(bytes);
267-
}
268-
269-
Asn1Object read() throws IOException {
270-
int tag = in.read();
271-
272-
if (tag == -1) {
273-
throw new IOException("Invalid DER: stream too short, missing tag");
274-
}
275-
276-
int length = getLength();
277-
byte[] value = new byte[length];
278-
if (in.read(value) < length) {
279-
throw new IOException("Invalid DER: stream too short, missing value");
280-
}
281-
282-
return new Asn1Object(tag, value);
283-
}
284-
285-
private int getLength() throws IOException {
286-
int i = in.read();
287-
if (i == -1) {
288-
throw new IOException("Invalid DER: length missing");
289-
}
290-
291-
if ((i & ~0x7F) == 0) {
292-
return i;
293-
}
294-
295-
int num = i & 0x7F;
296-
if (i >= 0xFF || num > 4) {
297-
throw new IOException("Invalid DER: length field too big (" + i + ")");
298-
}
299-
300-
byte[] bytes = new byte[num];
301-
if (in.read(bytes) < num) {
302-
throw new IOException("Invalid DER: length too short");
303-
}
304-
305-
return new BigInteger(1, bytes).intValue();
306-
}
307-
}
308-
309-
static class Asn1Object {
310-
311-
private final int type;
312-
private final byte[] value;
313-
private final int tag;
314-
315-
public Asn1Object(int tag, byte[] value) {
316-
this.tag = tag;
317-
this.type = tag & 0x1F;
318-
this.value = value;
319-
}
320-
321-
public byte[] getValue() {
322-
return value;
323-
}
324-
325-
BigInteger getInteger() throws IOException {
326-
if (type != 0x02) {
327-
throw new IOException("Invalid DER: object is not integer"); // $NON-NLS-1$
328-
}
329-
return new BigInteger(value);
330-
}
331-
332-
void validateSequence() throws IOException {
333-
if (type != 0x10) {
334-
throw new IOException("Invalid DER: not a sequence");
335-
}
336-
if ((tag & 0x20) != 0x20) {
337-
throw new IOException("Invalid DER: can't parse primitive entity");
338-
}
339-
}
340-
}
341-
342193
private static void loadDefaultKeyStoreFile(KeyStore keyStore, char[] keyStorePassphrase)
343194
throws CertificateException, NoSuchAlgorithmException, IOException {
344195

util/src/test/java/io/kubernetes/client/util/SSLUtilsTest.java

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,48 +13,75 @@
1313
package io.kubernetes.client.util;
1414

1515
import io.kubernetes.client.Resources;
16-
import java.io.File;
1716
import java.io.IOException;
18-
import java.nio.file.Files;
19-
import java.nio.file.Paths;
20-
import java.security.NoSuchAlgorithmException;
17+
import java.net.URL;
2118
import java.security.PrivateKey;
2219
import java.security.spec.InvalidKeySpecException;
23-
import junit.framework.TestCase;
24-
25-
public class SSLUtilsTest extends TestCase {
26-
27-
private static final String CLIENT_KEY_PATH =
28-
new File(Resources.getResource("clientauth.key").getPath()).toString();
29-
private static final String CLIENT_KEY_RSA_PATH =
30-
new File(Resources.getResource("clientauth-rsa.key").getPath()).toString();
31-
private static final String CLIENT_KEY_EC_PATH =
32-
new File(Resources.getResource("clientauth-ec.key").getPath()).toString();
33-
34-
public void testPKCS8KeyLoadDump()
35-
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
36-
byte[] loaded = Files.readAllBytes(Paths.get(CLIENT_KEY_PATH.replace("C:/", "")));
37-
PrivateKey privateKey = SSLUtils.loadKey(loaded);
38-
byte[] dumped = SSLUtils.dumpKey(privateKey);
39-
PrivateKey reloaded = SSLUtils.loadKey(dumped);
40-
assertEquals(privateKey, reloaded);
20+
import org.apache.commons.io.IOUtils;
21+
import org.junit.Test;
22+
23+
import static org.junit.Assert.assertEquals;
24+
25+
public class SSLUtilsTest {
26+
27+
private static final String CLIENT_CERT = "clientauth.cert";
28+
29+
private static final String CLIENT_KEY_RSA_PKCS8 = "clientauth.key";
30+
31+
private static final String CLIENT_KEY_RSA_PKCS1 = "clientauth-rsa.key";
32+
33+
private static final String CLIENT_KEY_ECDSA_PKCS7 = "clientauth-ec.key";
34+
35+
private static final String CLIENT_KEY_ECDSA_PKCS8 = "clientauth-ec-fixed.key";
36+
37+
private static final String RSA_ALGORITHM = "RSA";
38+
39+
private static final String ECDSA_ALGORITHM = "ECDSA";
40+
41+
@Test
42+
public void testLoadKeyRsaPkcs8() throws IOException, InvalidKeySpecException {
43+
final PrivateKey privateKey = assertLoadDumpReloadKeyEquals(CLIENT_KEY_RSA_PKCS8);
44+
assertEquals(RSA_ALGORITHM, privateKey.getAlgorithm());
4145
}
4246

43-
public void testPKCS1RSAKeyLoadDump()
44-
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
45-
byte[] loaded = Files.readAllBytes(Paths.get(CLIENT_KEY_RSA_PATH));
46-
PrivateKey privateKey = SSLUtils.loadKey(loaded);
47-
byte[] dumped = SSLUtils.dumpKey(privateKey);
48-
PrivateKey reloaded = SSLUtils.loadKey(dumped);
49-
assertEquals(privateKey, reloaded);
47+
@Test
48+
public void testLoadKeyRsaPkcs1() throws IOException, InvalidKeySpecException {
49+
final PrivateKey privateKey = assertLoadDumpReloadKeyEquals(CLIENT_KEY_RSA_PKCS1);
50+
assertEquals(RSA_ALGORITHM, privateKey.getAlgorithm());
51+
}
52+
53+
@Test
54+
public void testLoadKeyEcdsaPkcs7() throws IOException, InvalidKeySpecException {
55+
final PrivateKey privateKey = assertLoadDumpReloadKeyEquals(CLIENT_KEY_ECDSA_PKCS7);
56+
assertEquals(ECDSA_ALGORITHM, privateKey.getAlgorithm());
57+
}
58+
59+
@Test
60+
public void testLoadKeyEcdsaPkcs8() throws IOException, InvalidKeySpecException {
61+
final PrivateKey privateKey = assertLoadDumpReloadKeyEquals(CLIENT_KEY_ECDSA_PKCS8);
62+
assertEquals(ECDSA_ALGORITHM, privateKey.getAlgorithm());
63+
}
64+
65+
@Test(expected = InvalidKeySpecException.class)
66+
public void testLoadKeyCertificateNotSupported() throws IOException, InvalidKeySpecException {
67+
final byte[] resourceBytes = getResourceBytes(CLIENT_CERT);
68+
SSLUtils.loadKey(resourceBytes);
69+
}
70+
71+
private PrivateKey assertLoadDumpReloadKeyEquals(final String filePath) throws IOException, InvalidKeySpecException {
72+
final byte[] resourceBytes = getResourceBytes(filePath);
73+
final PrivateKey privateKey = SSLUtils.loadKey(resourceBytes);
74+
75+
byte[] dumpedKey = SSLUtils.dumpKey(privateKey);
76+
final PrivateKey reloadedPrivateKey = SSLUtils.loadKey(dumpedKey);
77+
78+
assertEquals(privateKey, reloadedPrivateKey);
79+
80+
return privateKey;
5081
}
5182

52-
public void testPKCS1ECKeyLoadDump()
53-
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
54-
byte[] loaded = Files.readAllBytes(Paths.get(CLIENT_KEY_EC_PATH));
55-
PrivateKey privateKey = SSLUtils.loadKey(loaded);
56-
byte[] dumped = SSLUtils.dumpKey(privateKey);
57-
PrivateKey reloaded = SSLUtils.loadKey(dumped);
58-
assertEquals(privateKey, reloaded);
83+
private byte[] getResourceBytes(final String filePath) throws IOException {
84+
final URL resourceUrl = Resources.getResource(filePath);
85+
return IOUtils.toByteArray(resourceUrl);
5986
}
6087
}

0 commit comments

Comments
 (0)