1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.pkitesting;
17
18 import io.netty.util.internal.EmptyArrays;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.StandardOpenOption;
27 import java.security.KeyPair;
28 import java.security.KeyStore;
29 import java.security.KeyStoreException;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.UnrecoverableKeyException;
32 import java.security.cert.CertificateEncodingException;
33 import java.security.cert.CertificateException;
34 import java.security.cert.TrustAnchor;
35 import java.security.cert.X509Certificate;
36 import java.util.Arrays;
37 import java.util.Base64;
38 import java.util.List;
39 import javax.net.ssl.KeyManagerFactory;
40 import javax.net.ssl.TrustManager;
41 import javax.net.ssl.TrustManagerFactory;
42
43 import static java.util.Objects.requireNonNull;
44
45
46
47
48
49 public final class X509Bundle {
50 private final X509Certificate[] certPath;
51 private final X509Certificate root;
52 private final KeyPair keyPair;
53
54
55
56
57
58
59
60
61 private X509Bundle(X509Certificate[] certPath, X509Certificate root, KeyPair keyPair) {
62 requireNonNull(root, "root");
63 requireNonNull(keyPair, "keyPair");
64 if (certPath.length > 1 && certPath[certPath.length - 1].equals(root)) {
65 this.certPath = Arrays.copyOf(certPath, certPath.length - 1);
66 } else {
67 this.certPath = certPath.clone();
68 }
69 this.root = root;
70 this.keyPair = keyPair;
71 }
72
73
74
75
76
77
78
79 public static X509Bundle fromRootCertificateAuthority(X509Certificate root, KeyPair keyPair) {
80 requireNonNull(root, "root");
81 requireNonNull(keyPair, "keyPair");
82 X509Bundle bundle = new X509Bundle(new X509Certificate[]{root}, root, keyPair);
83 if (!bundle.isCertificateAuthority() || !bundle.isSelfSigned()) {
84 throw new IllegalArgumentException("Given certificate is not a root CA certificate: " +
85 root.getSubjectX500Principal() + ", issued by " + root.getIssuerX500Principal());
86 }
87 return bundle;
88 }
89
90
91
92
93
94
95
96
97 public static X509Bundle fromCertificatePath(
98 X509Certificate[] certPath, X509Certificate root, KeyPair keyPair) {
99 return new X509Bundle(certPath, root, keyPair);
100 }
101
102
103
104
105
106
107 public X509Certificate getCertificate() {
108 return certPath[0];
109 }
110
111
112
113
114
115 public String getCertificatePEM() {
116 return toCertPem(certPath[0]);
117 }
118
119
120
121
122
123 public X509Certificate[] getCertificatePath() {
124 return certPath.clone();
125 }
126
127
128
129
130
131 public X509Certificate[] getCertificatePathWithRoot() {
132 X509Certificate[] path = Arrays.copyOf(certPath, certPath.length + 1);
133 path[path.length - 1] = root;
134 return path;
135 }
136
137
138
139
140
141 public List<X509Certificate> getCertificatePathList() {
142 return Arrays.asList(certPath);
143 }
144
145
146
147
148
149 public String getCertificatePathPEM() {
150 return toCertPem(certPath);
151 }
152
153
154
155
156
157 public KeyPair getKeyPair() {
158 return keyPair;
159 }
160
161
162
163
164
165 public X509Certificate getRootCertificate() {
166 return root;
167 }
168
169
170
171
172
173 public String getRootCertificatePEM() {
174 return toCertPem(root);
175 }
176
177 private static String toCertPem(X509Certificate... certs) {
178 Base64.Encoder encoder = getMimeEncoder();
179 StringBuilder sb = new StringBuilder();
180 for (X509Certificate cert : certs) {
181 sb.append("-----BEGIN CERTIFICATE-----\r\n");
182 try {
183 sb.append(encoder.encodeToString(cert.getEncoded()));
184 } catch (CertificateEncodingException e) {
185 throw new IllegalStateException(e);
186 }
187 sb.append("\r\n-----END CERTIFICATE-----\r\n");
188 }
189 return sb.toString();
190 }
191
192
193
194
195
196 public String getPrivateKeyPEM() {
197 Base64.Encoder encoder = getMimeEncoder();
198 StringBuilder sb = new StringBuilder();
199 sb.append("-----BEGIN PRIVATE KEY-----\r\n");
200 sb.append(encoder.encodeToString(keyPair.getPrivate().getEncoded()));
201 sb.append("\r\n-----END PRIVATE KEY-----\r\n");
202 return sb.toString();
203 }
204
205 private static Base64.Encoder getMimeEncoder() {
206 return Base64.getMimeEncoder(64, new byte[]{ '\r', '\n' });
207 }
208
209
210
211
212
213
214
215 public TrustAnchor getTrustAnchor() {
216 return new TrustAnchor(root, root.getExtensionValue(CertificateBuilder.OID_X509_NAME_CONSTRAINTS));
217 }
218
219
220
221
222
223
224 public boolean isCertificateAuthority() {
225 return certPath[0].getBasicConstraints() != -1;
226 }
227
228
229
230
231
232 public boolean isSelfSigned() {
233 X509Certificate leaf = certPath[0];
234 return certPath.length == 1 &&
235 leaf.getSubjectX500Principal().equals(leaf.getIssuerX500Principal()) &&
236 Arrays.equals(leaf.getSubjectUniqueID(), leaf.getIssuerUniqueID());
237 }
238
239
240
241
242
243 public TrustManager toTrustManager() {
244 TrustManagerFactory tmf = toTrustManagerFactory();
245 return tmf.getTrustManagers()[0];
246 }
247
248
249
250
251
252
253
254
255 public TrustManagerFactory toTrustManagerFactory() {
256 return toTrustManagerFactory(TrustManagerFactory.getDefaultAlgorithm());
257 }
258
259
260
261
262
263
264
265 public TrustManagerFactory toTrustManagerFactory(String algorithm) {
266 try {
267 TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
268 tmf.init(toKeyStore(EmptyArrays.EMPTY_CHARS));
269 return tmf;
270 } catch (NoSuchAlgorithmException e) {
271 throw new AssertionError("Default TrustManagerFactory algorithm was not available.", e);
272 } catch (KeyStoreException e) {
273 throw new IllegalStateException("Failed to initialize TrustManagerFactory with KeyStore.", e);
274 }
275 }
276
277
278
279
280
281
282
283
284
285
286
287
288
289 public KeyStore toKeyStore(char[] keyEntryPassword) throws KeyStoreException {
290 return toKeyStore("PKCS12", keyEntryPassword);
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304
305 public KeyStore toKeyStore(String algorithm, char[] keyEntryPassword) throws KeyStoreException {
306 KeyStore keyStore;
307 try {
308 keyStore = KeyStore.getInstance(algorithm);
309 keyStore.load(null, null);
310 } catch (IOException | NoSuchAlgorithmException | CertificateException e) {
311 throw new KeyStoreException("Failed to initialize '" + algorithm + "' KeyStore.", e);
312 }
313 keyStore.setCertificateEntry("1", root);
314 if (keyPair.getPrivate() != null) {
315 keyStore.setKeyEntry("2", keyPair.getPrivate(), keyEntryPassword, certPath);
316 }
317 return keyStore;
318 }
319
320
321
322
323
324
325
326
327
328 public File toTempKeyStoreFile(char[] password) throws Exception {
329 return toTempKeyStoreFile(password, password);
330 }
331
332
333
334
335
336
337
338
339
340 public File toTempKeyStoreFile(char[] pkcs12Password, char[] keyEntryPassword) throws Exception {
341 KeyStore keyStore = toKeyStore(keyEntryPassword);
342 Path tempFile = Files.createTempFile("ks", ".p12");
343 try (OutputStream out = Files.newOutputStream(tempFile, StandardOpenOption.WRITE)) {
344 keyStore.store(out, pkcs12Password);
345 }
346 File file = tempFile.toFile();
347 file.deleteOnExit();
348 return file;
349 }
350
351
352
353
354
355
356
357 public File toTempRootCertPem() throws IOException {
358 return createTempPemFile(getRootCertificatePEM(), "ca");
359 }
360
361
362
363
364
365
366
367 public File toTempCertChainPem() throws IOException {
368 return createTempPemFile(getCertificatePathPEM(), "chain");
369 }
370
371
372
373
374
375
376
377 public File toTempPrivateKeyPem() throws IOException {
378 return createTempPemFile(getPrivateKeyPEM(), "key");
379 }
380
381 private static File createTempPemFile(String pem, String filePrefix) throws IOException {
382 Path tempFile = Files.createTempFile(filePrefix, ".pem");
383 try (OutputStream out = Files.newOutputStream(tempFile, StandardOpenOption.WRITE)) {
384 out.write(pem.getBytes(StandardCharsets.ISO_8859_1));
385 }
386 File file = tempFile.toFile();
387 file.deleteOnExit();
388 return file;
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 public KeyManagerFactory toKeyManagerFactory()
405 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
406 return toKeyManagerFactory(KeyManagerFactory.getDefaultAlgorithm());
407 }
408
409
410
411
412
413
414
415
416
417
418
419 public KeyManagerFactory toKeyManagerFactory(String algorithm)
420 throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
421 KeyManagerFactory kmf;
422 try {
423 kmf = KeyManagerFactory.getInstance(algorithm);
424 } catch (NoSuchAlgorithmException e) {
425 throw new AssertionError("Default KeyManagerFactory algorithm was not available.", e);
426 }
427 kmf.init(toKeyStore(EmptyArrays.EMPTY_CHARS), EmptyArrays.EMPTY_CHARS);
428 return kmf;
429 }
430 }