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 logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t);
244 try {
245
246 paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter, algorithm);
247 } catch (Throwable t2) {
248 logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t2);
249 final CertificateException certificateException = new CertificateException(
250 "No provider succeeded to generate a self-signed certificate. " +
251 "See debug log for the root cause.", t2);
252 ThrowableUtil.addSuppressed(certificateException, t);
253 throw certificateException;
254 }
255 }
256
257 certificate = new File(paths[0]);
258 privateKey = new File(paths[1]);
259 key = keypair.getPrivate();
260 FileInputStream certificateInput = null;
261 try {
262 certificateInput = new FileInputStream(certificate);
263 cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput);
264 } catch (Exception e) {
265 throw new CertificateEncodingException(e);
266 } finally {
267 if (certificateInput != null) {
268 try {
269 certificateInput.close();
270 } catch (IOException e) {
271 if (logger.isWarnEnabled()) {
272 logger.warn("Failed to close a file: " + certificate, e);
273 }
274 }
275 }
276 }
277 }
278
279
280
281
282 public File certificate() {
283 return certificate;
284 }
285
286
287
288
289 public File privateKey() {
290 return privateKey;
291 }
292
293
294
295
296 public X509Certificate cert() {
297 return cert;
298 }
299
300
301
302
303 public PrivateKey key() {
304 return key;
305 }
306
307
308
309
310 public void delete() {
311 safeDelete(certificate);
312 safeDelete(privateKey);
313 }
314
315 static String[] newSelfSignedCertificate(
316 String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException {
317
318 ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded());
319 ByteBuf encodedBuf;
320 final String keyText;
321 try {
322 encodedBuf = Base64.encode(wrappedBuf, true);
323 try {
324 keyText = "-----BEGIN PRIVATE KEY-----\n" +
325 encodedBuf.toString(CharsetUtil.US_ASCII) +
326 "\n-----END PRIVATE KEY-----\n";
327 } finally {
328 encodedBuf.release();
329 }
330 } finally {
331 wrappedBuf.release();
332 }
333
334
335 fqdn = fqdn.replaceAll("[^\\w.-]", "x");
336
337 File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null);
338 keyFile.deleteOnExit();
339
340 OutputStream keyOut = new FileOutputStream(keyFile);
341 try {
342 keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII));
343 keyOut.close();
344 keyOut = null;
345 } finally {
346 if (keyOut != null) {
347 safeClose(keyFile, keyOut);
348 safeDelete(keyFile);
349 }
350 }
351
352 wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded());
353 final String certText;
354 try {
355 encodedBuf = Base64.encode(wrappedBuf, true);
356 try {
357
358 certText = "-----BEGIN CERTIFICATE-----\n" +
359 encodedBuf.toString(CharsetUtil.US_ASCII) +
360 "\n-----END CERTIFICATE-----\n";
361 } finally {
362 encodedBuf.release();
363 }
364 } finally {
365 wrappedBuf.release();
366 }
367
368 File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null);
369 certFile.deleteOnExit();
370
371 OutputStream certOut = new FileOutputStream(certFile);
372 try {
373 certOut.write(certText.getBytes(CharsetUtil.US_ASCII));
374 certOut.close();
375 certOut = null;
376 } finally {
377 if (certOut != null) {
378 safeClose(certFile, certOut);
379 safeDelete(certFile);
380 safeDelete(keyFile);
381 }
382 }
383
384 return new String[] { certFile.getPath(), keyFile.getPath() };
385 }
386
387 private static void safeDelete(File certFile) {
388 if (!certFile.delete()) {
389 if (logger.isWarnEnabled()) {
390 logger.warn("Failed to delete a file: " + certFile);
391 }
392 }
393 }
394
395 private static void safeClose(File keyFile, OutputStream keyOut) {
396 try {
397 keyOut.close();
398 } catch (IOException e) {
399 if (logger.isWarnEnabled()) {
400 logger.warn("Failed to close a file: " + keyFile, e);
401 }
402 }
403 }
404 }