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