1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.ssl.util;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.handler.codec.base64.Base64;
20 import io.netty5.util.CharsetUtil;
21 import io.netty5.util.internal.PlatformDependent;
22 import io.netty5.util.internal.SystemPropertyUtil;
23 import io.netty5.util.internal.logging.InternalLogger;
24 import io.netty5.util.internal.logging.InternalLoggerFactory;
25
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.security.KeyPair;
32 import java.security.KeyPairGenerator;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.PrivateKey;
35 import java.security.SecureRandom;
36 import java.security.cert.CertificateEncodingException;
37 import java.security.cert.CertificateException;
38 import java.security.cert.CertificateFactory;
39 import java.security.cert.X509Certificate;
40 import java.util.Date;
41
42 import static io.netty5.buffer.api.DefaultBufferAllocators.onHeapAllocator;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 public final class SelfSignedCertificate {
61
62 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SelfSignedCertificate.class);
63
64
65 private static final Date DEFAULT_NOT_BEFORE = new Date(SystemPropertyUtil.getLong(
66 "io.netty5.selfSignedCertificate.defaultNotBefore", System.currentTimeMillis() - 86400000L * 365));
67
68 private static final Date DEFAULT_NOT_AFTER = new Date(SystemPropertyUtil.getLong(
69 "io.netty5.selfSignedCertificate.defaultNotAfter", 253402300799000L));
70
71
72
73
74
75
76 private static final int DEFAULT_KEY_LENGTH_BITS =
77 SystemPropertyUtil.getInt("io.netty5.handler.ssl.util.selfSignedKeyStrength", 2048);
78
79 private final File certificate;
80 private final File privateKey;
81 private final X509Certificate cert;
82 private final PrivateKey key;
83
84
85
86
87
88 public SelfSignedCertificate() throws CertificateException {
89 this(DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
90 }
91
92
93
94
95
96
97
98
99 public SelfSignedCertificate(Date notBefore, Date notAfter)
100 throws CertificateException {
101 this("localhost", notBefore, notAfter, "RSA", DEFAULT_KEY_LENGTH_BITS);
102 }
103
104
105
106
107
108
109
110
111
112 public SelfSignedCertificate(Date notBefore, Date notAfter, String algorithm, int bits)
113 throws CertificateException {
114 this("localhost", notBefore, notAfter, algorithm, bits);
115 }
116
117
118
119
120
121
122
123 public SelfSignedCertificate(String fqdn) throws CertificateException {
124 this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
125 }
126
127
128
129
130
131
132
133
134 public SelfSignedCertificate(String fqdn, String algorithm, int bits) throws CertificateException {
135 this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm, bits);
136 }
137
138
139
140
141
142
143
144
145
146 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException {
147
148
149 this(fqdn, ThreadLocalInsecureRandom.current(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter, "RSA");
150 }
151
152
153
154
155
156
157
158
159
160
161 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter, String algorithm, int bits)
162 throws CertificateException {
163
164
165 this(fqdn, ThreadLocalInsecureRandom.current(), bits, notBefore, notAfter, algorithm);
166 }
167
168
169
170
171
172
173
174
175
176 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits)
177 throws CertificateException {
178 this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA");
179 }
180
181
182
183
184
185
186
187
188
189 public SelfSignedCertificate(String fqdn, SecureRandom random, String algorithm, int bits)
190 throws CertificateException {
191 this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm);
192 }
193
194
195
196
197
198
199
200
201
202
203
204 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
205 throws CertificateException {
206 this(fqdn, random, bits, notBefore, notAfter, "RSA");
207 }
208
209
210
211
212
213
214
215
216
217
218
219 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter,
220 String algorithm) throws CertificateException {
221
222 if (!"EC".equalsIgnoreCase(algorithm) && !"RSA".equalsIgnoreCase(algorithm)) {
223 throw new IllegalArgumentException("Algorithm not valid: " + algorithm);
224 }
225
226 final KeyPair keypair;
227 try {
228 KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
229 keyGen.initialize(bits, random);
230 keypair = keyGen.generateKeyPair();
231 } catch (NoSuchAlgorithmException e) {
232
233 throw new Error(e);
234 }
235
236 String[] paths;
237 try {
238 paths = BouncyCastleSelfSignedCertGenerator.generate(
239 fqdn, keypair, random, notBefore, notAfter, algorithm);
240 } catch (Throwable throwable) {
241 logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", throwable);
242 throw new CertificateException(
243 "No provider succeeded to generate a self-signed certificate. " +
244 "See debug log for the root cause.", throwable);
245 }
246
247 certificate = new File(paths[0]);
248 privateKey = new File(paths[1]);
249 key = keypair.getPrivate();
250 FileInputStream certificateInput = null;
251 try {
252 certificateInput = new FileInputStream(certificate);
253 cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput);
254 } catch (Exception e) {
255 throw new CertificateEncodingException(e);
256 } finally {
257 if (certificateInput != null) {
258 try {
259 certificateInput.close();
260 } catch (IOException e) {
261 if (logger.isWarnEnabled()) {
262 logger.warn("Failed to close a file: " + certificate, e);
263 }
264 }
265 }
266 }
267 }
268
269
270
271
272 public File certificate() {
273 return certificate;
274 }
275
276
277
278
279 public File privateKey() {
280 return privateKey;
281 }
282
283
284
285
286 public X509Certificate cert() {
287 return cert;
288 }
289
290
291
292
293 public PrivateKey key() {
294 return key;
295 }
296
297
298
299
300 public void delete() {
301 safeDelete(certificate);
302 safeDelete(privateKey);
303 }
304
305 static String[] newSelfSignedCertificate(
306 String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException {
307
308 final String keyText;
309 try (Buffer wrappedBuf = onHeapAllocator().copyOf(key.getEncoded());
310 Buffer encodedBuf = Base64.encode(wrappedBuf, true)) {
311 keyText = "-----BEGIN PRIVATE KEY-----\n" +
312 encodedBuf.toString(CharsetUtil.US_ASCII) +
313 "\n-----END PRIVATE KEY-----\n";
314 }
315
316
317 fqdn = fqdn.replaceAll("[^\\w.-]", "x");
318
319 File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null);
320 keyFile.deleteOnExit();
321
322 OutputStream keyOut = new FileOutputStream(keyFile);
323 try {
324 keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII));
325 keyOut.close();
326 keyOut = null;
327 } finally {
328 if (keyOut != null) {
329 safeClose(keyFile, keyOut);
330 safeDelete(keyFile);
331 }
332 }
333
334 final String certText;
335 try (Buffer wrappedBuf = onHeapAllocator().copyOf(cert.getEncoded());
336 Buffer encodedBuf = Base64.encode(wrappedBuf, true)) {
337
338 certText = "-----BEGIN CERTIFICATE-----\n" +
339 encodedBuf.toString(CharsetUtil.US_ASCII) +
340 "\n-----END CERTIFICATE-----\n";
341 }
342
343 File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null);
344 certFile.deleteOnExit();
345
346 OutputStream certOut = new FileOutputStream(certFile);
347 try {
348 certOut.write(certText.getBytes(CharsetUtil.US_ASCII));
349 certOut.close();
350 certOut = null;
351 } finally {
352 if (certOut != null) {
353 safeClose(certFile, certOut);
354 safeDelete(certFile);
355 safeDelete(keyFile);
356 }
357 }
358
359 return new String[] { certFile.getPath(), keyFile.getPath() };
360 }
361
362 private static void safeDelete(File certFile) {
363 if (!certFile.delete()) {
364 if (logger.isWarnEnabled()) {
365 logger.warn("Failed to delete a file: " + certFile);
366 }
367 }
368 }
369
370 private static void safeClose(File keyFile, OutputStream keyOut) {
371 try {
372 keyOut.close();
373 } catch (IOException e) {
374 if (logger.isWarnEnabled()) {
375 logger.warn("Failed to close a file: " + keyFile, e);
376 }
377 }
378 }
379 }