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.PlatformDependent;
24 import io.netty.util.internal.SystemPropertyUtil;
25 import io.netty.util.internal.ThrowableUtil;
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(DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
91 }
92
93
94
95
96
97
98
99
100 public SelfSignedCertificate(Date notBefore, Date notAfter)
101 throws CertificateException {
102 this("localhost", notBefore, notAfter, "RSA", DEFAULT_KEY_LENGTH_BITS);
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("localhost", notBefore, notAfter, algorithm, bits);
116 }
117
118
119
120
121
122
123
124 public SelfSignedCertificate(String fqdn) throws CertificateException {
125 this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
126 }
127
128
129
130
131
132
133
134
135 public SelfSignedCertificate(String fqdn, String algorithm, int bits) throws CertificateException {
136 this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm, bits);
137 }
138
139
140
141
142
143
144
145
146
147 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException {
148
149
150 this(fqdn, ThreadLocalInsecureRandom.current(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter, "RSA");
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
165
166 this(fqdn, ThreadLocalInsecureRandom.current(), bits, notBefore, notAfter, algorithm);
167 }
168
169
170
171
172
173
174
175
176
177 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits)
178 throws CertificateException {
179 this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA");
180 }
181
182
183
184
185
186
187
188
189
190 public SelfSignedCertificate(String fqdn, SecureRandom random, String algorithm, int bits)
191 throws CertificateException {
192 this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm);
193 }
194
195
196
197
198
199
200
201
202
203
204
205 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
206 throws CertificateException {
207 this(fqdn, random, bits, notBefore, notAfter, "RSA");
208 }
209
210
211
212
213
214
215
216
217
218
219
220 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter,
221 String algorithm) throws CertificateException {
222
223 if (!"EC".equalsIgnoreCase(algorithm) && !"RSA".equalsIgnoreCase(algorithm)) {
224 throw new IllegalArgumentException("Algorithm not valid: " + algorithm);
225 }
226
227 final KeyPair keypair;
228 try {
229 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
230 keyGen.initialize(bits, random);
231 keypair = keyGen.generateKeyPair();
232 } catch (NoSuchAlgorithmException e) {
233
234 throw new Error(e);
235 }
236
237 String[] paths;
238 try {
239
240 paths = BouncyCastleSelfSignedCertGenerator.generate(
241 fqdn, keypair, random, notBefore, notAfter, algorithm);
242 } catch (Throwable t) {
243 if (!isBouncyCastleAvailable()) {
244 logger.debug("Failed to generate a self-signed X.509 certificate because " +
245 "BouncyCastle PKIX is not available in classpath");
246 } else {
247 logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t);
248 }
249 try {
250
251 paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter, algorithm);
252 } catch (Throwable t2) {
253 logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t2);
254 final CertificateException certificateException = new CertificateException(
255 "No provider succeeded to generate a self-signed certificate. " +
256 "See debug log for the root cause.", t2);
257 ThrowableUtil.addSuppressed(certificateException, t);
258 throw certificateException;
259 }
260 }
261
262 certificate = new File(paths[0]);
263 privateKey = new File(paths[1]);
264 key = keypair.getPrivate();
265 FileInputStream certificateInput = null;
266 try {
267 certificateInput = new FileInputStream(certificate);
268 cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput);
269 } catch (Exception e) {
270 throw new CertificateEncodingException(e);
271 } finally {
272 if (certificateInput != null) {
273 try {
274 certificateInput.close();
275 } catch (IOException e) {
276 if (logger.isWarnEnabled()) {
277 logger.warn("Failed to close a file: " + certificate, e);
278 }
279 }
280 }
281 }
282 }
283
284
285
286
287 public File certificate() {
288 return certificate;
289 }
290
291
292
293
294 public File privateKey() {
295 return privateKey;
296 }
297
298
299
300
301 public X509Certificate cert() {
302 return cert;
303 }
304
305
306
307
308 public PrivateKey key() {
309 return key;
310 }
311
312
313
314
315 public void delete() {
316 safeDelete(certificate);
317 safeDelete(privateKey);
318 }
319
320 static String[] newSelfSignedCertificate(
321 String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException {
322
323 ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded());
324 ByteBuf encodedBuf;
325 final String keyText;
326 try {
327 encodedBuf = Base64.encode(wrappedBuf, true);
328 try {
329 keyText = "-----BEGIN PRIVATE KEY-----\n" +
330 encodedBuf.toString(CharsetUtil.US_ASCII) +
331 "\n-----END PRIVATE KEY-----\n";
332 } finally {
333 encodedBuf.release();
334 }
335 } finally {
336 wrappedBuf.release();
337 }
338
339
340 fqdn = fqdn.replaceAll("[^\\w.-]", "x");
341
342 File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null);
343 keyFile.deleteOnExit();
344
345 OutputStream keyOut = new FileOutputStream(keyFile);
346 try {
347 keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII));
348 keyOut.close();
349 keyOut = null;
350 } finally {
351 if (keyOut != null) {
352 safeClose(keyFile, keyOut);
353 safeDelete(keyFile);
354 }
355 }
356
357 wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded());
358 final String certText;
359 try {
360 encodedBuf = Base64.encode(wrappedBuf, true);
361 try {
362
363 certText = "-----BEGIN CERTIFICATE-----\n" +
364 encodedBuf.toString(CharsetUtil.US_ASCII) +
365 "\n-----END CERTIFICATE-----\n";
366 } finally {
367 encodedBuf.release();
368 }
369 } finally {
370 wrappedBuf.release();
371 }
372
373 File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null);
374 certFile.deleteOnExit();
375
376 OutputStream certOut = new FileOutputStream(certFile);
377 try {
378 certOut.write(certText.getBytes(CharsetUtil.US_ASCII));
379 certOut.close();
380 certOut = null;
381 } finally {
382 if (certOut != null) {
383 safeClose(certFile, certOut);
384 safeDelete(certFile);
385 safeDelete(keyFile);
386 }
387 }
388
389 return new String[] { certFile.getPath(), keyFile.getPath() };
390 }
391
392 private static void safeDelete(File certFile) {
393 if (!certFile.delete()) {
394 if (logger.isWarnEnabled()) {
395 logger.warn("Failed to delete a file: " + certFile);
396 }
397 }
398 }
399
400 private static void safeClose(File keyFile, OutputStream keyOut) {
401 try {
402 keyOut.close();
403 } catch (IOException e) {
404 if (logger.isWarnEnabled()) {
405 logger.warn("Failed to close a file: " + keyFile, e);
406 }
407 }
408 }
409
410 private static boolean isBouncyCastleAvailable() {
411 try {
412 Class.forName("org.bouncycastle.cert.X509v3CertificateBuilder");
413 return true;
414 } catch (ClassNotFoundException e) {
415 return false;
416 }
417 }
418 }