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