1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.ssl.util;
18
19 import io.netty.buffer.ByteBuf;
20 import io.netty.buffer.Unpooled;
21 import io.netty.handler.codec.base64.Base64;
22 import io.netty.util.CharsetUtil;
23 import io.netty.util.internal.ObjectUtil;
24 import io.netty.util.internal.PlatformDependent;
25 import io.netty.util.internal.SystemPropertyUtil;
26 import io.netty.util.internal.logging.InternalLogger;
27 import io.netty.util.internal.logging.InternalLoggerFactory;
28
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.security.KeyPair;
35 import java.security.KeyPairGenerator;
36 import java.security.NoSuchAlgorithmException;
37 import java.security.PrivateKey;
38 import java.security.SecureRandom;
39 import java.security.cert.CertificateEncodingException;
40 import java.security.cert.CertificateException;
41 import java.security.cert.CertificateFactory;
42 import java.security.cert.X509Certificate;
43 import java.util.Date;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public final class SelfSignedCertificate {
62
63 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SelfSignedCertificate.class);
64
65
66 private static final Date DEFAULT_NOT_BEFORE = new Date(SystemPropertyUtil.getLong(
67 "io.netty.selfSignedCertificate.defaultNotBefore", System.currentTimeMillis() - 86400000L * 365));
68
69 private static final Date DEFAULT_NOT_AFTER = new Date(SystemPropertyUtil.getLong(
70 "io.netty.selfSignedCertificate.defaultNotAfter", 253402300799000L));
71
72
73
74
75
76
77 private static final int DEFAULT_KEY_LENGTH_BITS =
78 SystemPropertyUtil.getInt("io.netty.handler.ssl.util.selfSignedKeyStrength", 2048);
79
80 private final File certificate;
81 private final File privateKey;
82 private final X509Certificate cert;
83 private final PrivateKey key;
84
85
86
87
88
89 public SelfSignedCertificate() throws CertificateException {
90 this(new Builder());
91 }
92
93
94
95
96
97
98
99
100 public SelfSignedCertificate(Date notBefore, Date notAfter)
101 throws CertificateException {
102 this(new Builder().notBefore(notBefore).notAfter(notAfter));
103 }
104
105
106
107
108
109
110
111
112
113 public SelfSignedCertificate(Date notBefore, Date notAfter, String algorithm, int bits)
114 throws CertificateException {
115 this(new Builder().notBefore(notBefore).notAfter(notAfter).algorithm(algorithm).bits(bits));
116 }
117
118
119
120
121
122
123
124 public SelfSignedCertificate(String fqdn) throws CertificateException {
125 this(new Builder().fqdn(fqdn));
126 }
127
128
129
130
131
132
133
134
135 public SelfSignedCertificate(String fqdn, String algorithm, int bits) throws CertificateException {
136 this(new Builder().fqdn(fqdn).algorithm(algorithm).bits(bits));
137 }
138
139
140
141
142
143
144
145
146
147 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException {
148 this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter));
149 }
150
151
152
153
154
155
156
157
158
159
160 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter, String algorithm, int bits)
161 throws CertificateException {
162 this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter).algorithm(algorithm).bits(bits));
163 }
164
165
166
167
168
169
170
171
172
173 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits)
174 throws CertificateException {
175 this(new Builder().fqdn(fqdn).random(random).bits(bits));
176 }
177
178
179
180
181
182
183
184
185
186 public SelfSignedCertificate(String fqdn, SecureRandom random, String algorithm, int bits)
187 throws CertificateException {
188 this(new Builder().fqdn(fqdn).random(random).algorithm(algorithm).bits(bits));
189 }
190
191
192
193
194
195
196
197
198
199
200
201 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
202 throws CertificateException {
203 this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter).random(random).bits(bits));
204 }
205
206
207
208
209
210
211
212
213
214
215
216 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter,
217 String algorithm) throws CertificateException {
218 this(new Builder().fqdn(fqdn).random(random).algorithm(algorithm).bits(bits)
219 .notBefore(notBefore).notAfter(notAfter));
220 }
221
222 private SelfSignedCertificate(Builder builder) throws CertificateException {
223 if (!builder.generateBc()) {
224 if (!builder.generateKeytool()) {
225 if (!builder.generateSunMiscSecurity()) {
226
227 throw (CertificateException) builder.failure;
228 }
229 }
230 }
231
232 certificate = new File(builder.paths[0]);
233 privateKey = new File(builder.paths[1]);
234 key = builder.privateKey;
235 try (FileInputStream certificateInput = new FileInputStream(certificate)) {
236 cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput);
237 } catch (Exception e) {
238 throw new CertificateEncodingException(e);
239 }
240 }
241
242 public static Builder builder() {
243 return new Builder();
244 }
245
246
247
248
249 public File certificate() {
250 return certificate;
251 }
252
253
254
255
256 public File privateKey() {
257 return privateKey;
258 }
259
260
261
262
263 public X509Certificate cert() {
264 return cert;
265 }
266
267
268
269
270 public PrivateKey key() {
271 return key;
272 }
273
274
275
276
277 public void delete() {
278 safeDelete(certificate);
279 safeDelete(privateKey);
280 }
281
282 static String[] newSelfSignedCertificate(
283 String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException {
284
285 ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded());
286 ByteBuf encodedBuf;
287 final String keyText;
288 try {
289 encodedBuf = Base64.encode(wrappedBuf, true);
290 try {
291 keyText = "-----BEGIN PRIVATE KEY-----\n" +
292 encodedBuf.toString(CharsetUtil.US_ASCII) +
293 "\n-----END PRIVATE KEY-----\n";
294 } finally {
295 encodedBuf.release();
296 }
297 } finally {
298 wrappedBuf.release();
299 }
300
301
302 fqdn = fqdn.replaceAll("[^\\w.-]", "x");
303
304 File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null);
305 keyFile.deleteOnExit();
306
307 OutputStream keyOut = new FileOutputStream(keyFile);
308 try {
309 keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII));
310 keyOut.close();
311 keyOut = null;
312 } finally {
313 if (keyOut != null) {
314 safeClose(keyFile, keyOut);
315 safeDelete(keyFile);
316 }
317 }
318
319 wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded());
320 final String certText;
321 try {
322 encodedBuf = Base64.encode(wrappedBuf, true);
323 try {
324
325 certText = "-----BEGIN CERTIFICATE-----\n" +
326 encodedBuf.toString(CharsetUtil.US_ASCII) +
327 "\n-----END CERTIFICATE-----\n";
328 } finally {
329 encodedBuf.release();
330 }
331 } finally {
332 wrappedBuf.release();
333 }
334
335 File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null);
336 certFile.deleteOnExit();
337
338 OutputStream certOut = new FileOutputStream(certFile);
339 try {
340 certOut.write(certText.getBytes(CharsetUtil.US_ASCII));
341 certOut.close();
342 certOut = null;
343 } finally {
344 if (certOut != null) {
345 safeClose(certFile, certOut);
346 safeDelete(certFile);
347 safeDelete(keyFile);
348 }
349 }
350
351 return new String[] { certFile.getPath(), keyFile.getPath() };
352 }
353
354 private static void safeDelete(File certFile) {
355 if (!certFile.delete()) {
356 if (logger.isWarnEnabled()) {
357 logger.warn("Failed to delete a file: " + certFile);
358 }
359 }
360 }
361
362 private static void safeClose(File keyFile, OutputStream keyOut) {
363 try {
364 keyOut.close();
365 } catch (IOException e) {
366 if (logger.isWarnEnabled()) {
367 logger.warn("Failed to close a file: " + keyFile, e);
368 }
369 }
370 }
371
372 private static boolean isBouncyCastleAvailable() {
373 try {
374
375 Class.forName("org.bouncycastle.cert.X509v3CertificateBuilder");
376 return true;
377 } catch (ClassNotFoundException e) {
378 return false;
379 }
380 }
381
382 public static final class Builder {
383
384 String fqdn = "localhost";
385 SecureRandom random;
386 int bits = DEFAULT_KEY_LENGTH_BITS;
387 Date notBefore = DEFAULT_NOT_BEFORE;
388 Date notAfter = DEFAULT_NOT_AFTER;
389 String algorithm = "RSA";
390
391
392 Throwable failure;
393 KeyPair keypair;
394 PrivateKey privateKey;
395 String[] paths;
396
397 private Builder() {
398 }
399
400
401
402
403
404
405
406 public Builder fqdn(String fqdn) {
407 this.fqdn = ObjectUtil.checkNotNullWithIAE(fqdn, "fqdn");
408 return this;
409 }
410
411
412
413
414
415
416
417 public Builder random(SecureRandom random) {
418 this.random = random;
419 return this;
420 }
421
422
423
424
425
426
427
428 public Builder bits(int bits) {
429 this.bits = bits;
430 return this;
431 }
432
433
434
435
436
437
438
439 public Builder notBefore(Date notBefore) {
440 this.notBefore = ObjectUtil.checkNotNullWithIAE(notBefore, "notBefore");
441 return this;
442 }
443
444
445
446
447
448
449
450 public Builder notAfter(Date notAfter) {
451 this.notAfter = ObjectUtil.checkNotNullWithIAE(notAfter, "notAfter");
452 return this;
453 }
454
455
456
457
458
459
460
461 public Builder algorithm(String algorithm) {
462 if ("EC".equalsIgnoreCase(algorithm)) {
463 this.algorithm = "EC";
464 } else if ("RSA".equalsIgnoreCase(algorithm)) {
465 this.algorithm = "RSA";
466 } else {
467 throw new IllegalArgumentException("Algorithm not valid: " + algorithm);
468 }
469 return this;
470 }
471
472 private SecureRandom randomOrDefault() {
473
474
475 return random == null ? ThreadLocalInsecureRandom.current() : random;
476 }
477
478 private void generateKeyPairLocally() {
479 if (keypair != null) {
480 return;
481 }
482
483 try {
484 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
485 keyGen.initialize(bits, randomOrDefault());
486 keypair = keyGen.generateKeyPair();
487 } catch (NoSuchAlgorithmException e) {
488
489 throw new IllegalStateException(e);
490 }
491 privateKey = keypair.getPrivate();
492 }
493
494 private void addFailure(Throwable t) {
495 if (failure != null) {
496 t.addSuppressed(failure);
497 }
498 failure = t;
499 }
500
501 boolean generateBc() {
502 if (!isBouncyCastleAvailable()) {
503
504 logger.debug("Failed to generate a self-signed X.509 certificate because " +
505 "BouncyCastle PKIX is not available in classpath");
506 return false;
507 }
508 generateKeyPairLocally();
509 try {
510
511 paths = BouncyCastleSelfSignedCertGenerator.generate(
512 fqdn, keypair, randomOrDefault(), notBefore, notAfter, algorithm);
513 return true;
514 } catch (Throwable t) {
515 logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t);
516 addFailure(t);
517 return false;
518 }
519 }
520
521 boolean generateKeytool() {
522 if (!KeytoolSelfSignedCertGenerator.isAvailable()) {
523 logger.debug("Not attempting to generate certificate with keytool because keytool is missing");
524 return false;
525 }
526 if (random != null) {
527 logger.debug("Not attempting to generate certificate with keytool because of explicitly set " +
528 "SecureRandom");
529 return false;
530 }
531 try {
532 KeytoolSelfSignedCertGenerator.generate(this);
533 return true;
534 } catch (Throwable t) {
535 logger.debug("Failed to generate a self-signed X.509 certificate using keytool:", t);
536 addFailure(t);
537 return false;
538 }
539 }
540
541 boolean generateSunMiscSecurity() {
542 generateKeyPairLocally();
543 try {
544
545 paths = OpenJdkSelfSignedCertGenerator.generate(
546 fqdn, keypair, randomOrDefault(), notBefore, notAfter, algorithm);
547 return true;
548 } catch (Throwable t2) {
549 logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t2);
550 final CertificateException certificateException = new CertificateException(
551 "No provider succeeded to generate a self-signed certificate. " +
552 "See debug log for the root cause.", t2);
553 addFailure(certificateException);
554 return false;
555 }
556 }
557
558
559
560
561
562
563
564 public SelfSignedCertificate build() throws CertificateException {
565 return new SelfSignedCertificate(this);
566 }
567 }
568 }