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.SystemPropertyUtil;
24 import io.netty.util.internal.logging.InternalLogger;
25 import io.netty.util.internal.logging.InternalLoggerFactory;
26
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.security.KeyPair;
33 import java.security.KeyPairGenerator;
34 import java.security.NoSuchAlgorithmException;
35 import java.security.PrivateKey;
36 import java.security.SecureRandom;
37 import java.security.cert.CertificateEncodingException;
38 import java.security.cert.CertificateException;
39 import java.security.cert.CertificateFactory;
40 import java.security.cert.X509Certificate;
41 import java.util.Date;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public final class SelfSignedCertificate {
60
61 private static final InternalLogger logger = InternalLoggerFactory.getInstance(SelfSignedCertificate.class);
62
63
64 private static final Date DEFAULT_NOT_BEFORE = new Date(SystemPropertyUtil.getLong(
65 "io.netty.selfSignedCertificate.defaultNotBefore", System.currentTimeMillis() - 86400000L * 365));
66
67 private static final Date DEFAULT_NOT_AFTER = new Date(SystemPropertyUtil.getLong(
68 "io.netty.selfSignedCertificate.defaultNotAfter", 253402300799000L));
69
70 private final File certificate;
71 private final File privateKey;
72 private final X509Certificate cert;
73 private final PrivateKey key;
74
75
76
77
78 public SelfSignedCertificate() throws CertificateException {
79 this(DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
80 }
81
82
83
84
85
86
87 public SelfSignedCertificate(Date notBefore, Date notAfter) throws CertificateException {
88 this("example.com", notBefore, notAfter);
89 }
90
91
92
93
94
95
96 public SelfSignedCertificate(String fqdn) throws CertificateException {
97 this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
98 }
99
100
101
102
103
104
105
106
107 public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException {
108
109
110 this(fqdn, ThreadLocalInsecureRandom.current(), 1024, notBefore, notAfter);
111 }
112
113
114
115
116
117
118
119
120 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits) throws CertificateException {
121 this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER);
122 }
123
124
125
126
127
128
129
130
131
132
133 public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
134 throws CertificateException {
135
136 final KeyPair keypair;
137 try {
138 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
139 keyGen.initialize(bits, random);
140 keypair = keyGen.generateKeyPair();
141 } catch (NoSuchAlgorithmException e) {
142
143 throw new Error(e);
144 }
145
146 String[] paths;
147 try {
148
149 paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter);
150 } catch (Throwable t) {
151 logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t);
152 try {
153
154 paths = BouncyCastleSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter);
155 } catch (Throwable t2) {
156 logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t2);
157 throw new CertificateException(
158 "No provider succeeded to generate a self-signed certificate. " +
159 "See debug log for the root cause.", t2);
160
161 }
162 }
163
164 certificate = new File(paths[0]);
165 privateKey = new File(paths[1]);
166 key = keypair.getPrivate();
167 FileInputStream certificateInput = null;
168 try {
169 certificateInput = new FileInputStream(certificate);
170 cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput);
171 } catch (Exception e) {
172 throw new CertificateEncodingException(e);
173 } finally {
174 if (certificateInput != null) {
175 try {
176 certificateInput.close();
177 } catch (IOException e) {
178 logger.warn("Failed to close a file: " + certificate, e);
179 }
180 }
181 }
182 }
183
184
185
186
187 public File certificate() {
188 return certificate;
189 }
190
191
192
193
194 public File privateKey() {
195 return privateKey;
196 }
197
198
199
200
201 public X509Certificate cert() {
202 return cert;
203 }
204
205
206
207
208 public PrivateKey key() {
209 return key;
210 }
211
212
213
214
215 public void delete() {
216 safeDelete(certificate);
217 safeDelete(privateKey);
218 }
219
220 static String[] newSelfSignedCertificate(
221 String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException {
222
223 ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded());
224 ByteBuf encodedBuf;
225 final String keyText;
226 try {
227 encodedBuf = Base64.encode(wrappedBuf, true);
228 try {
229 keyText = "-----BEGIN PRIVATE KEY-----\n" +
230 encodedBuf.toString(CharsetUtil.US_ASCII) +
231 "\n-----END PRIVATE KEY-----\n";
232 } finally {
233 encodedBuf.release();
234 }
235 } finally {
236 wrappedBuf.release();
237 }
238
239 File keyFile = File.createTempFile("keyutil_" + fqdn + '_', ".key");
240 keyFile.deleteOnExit();
241
242 OutputStream keyOut = new FileOutputStream(keyFile);
243 try {
244 keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII));
245 keyOut.close();
246 keyOut = null;
247 } finally {
248 if (keyOut != null) {
249 safeClose(keyFile, keyOut);
250 safeDelete(keyFile);
251 }
252 }
253
254 wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded());
255 final String certText;
256 try {
257 encodedBuf = Base64.encode(wrappedBuf, true);
258 try {
259
260 certText = "-----BEGIN CERTIFICATE-----\n" +
261 encodedBuf.toString(CharsetUtil.US_ASCII) +
262 "\n-----END CERTIFICATE-----\n";
263 } finally {
264 encodedBuf.release();
265 }
266 } finally {
267 wrappedBuf.release();
268 }
269
270 File certFile = File.createTempFile("keyutil_" + fqdn + '_', ".crt");
271 certFile.deleteOnExit();
272
273 OutputStream certOut = new FileOutputStream(certFile);
274 try {
275 certOut.write(certText.getBytes(CharsetUtil.US_ASCII));
276 certOut.close();
277 certOut = null;
278 } finally {
279 if (certOut != null) {
280 safeClose(certFile, certOut);
281 safeDelete(certFile);
282 safeDelete(keyFile);
283 }
284 }
285
286 return new String[] { certFile.getPath(), keyFile.getPath() };
287 }
288
289 private static void safeDelete(File certFile) {
290 if (!certFile.delete()) {
291 logger.warn("Failed to delete a file: " + certFile);
292 }
293 }
294
295 private static void safeClose(File keyFile, OutputStream keyOut) {
296 try {
297 keyOut.close();
298 } catch (IOException e) {
299 logger.warn("Failed to close a file: " + keyFile, e);
300 }
301 }
302 }