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.util.internal.PlatformDependent;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InterruptedIOException;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.Files;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.security.GeneralSecurityException;
31 import java.security.KeyStore;
32 import java.security.cert.X509Certificate;
33 import java.time.ZoneId;
34 import java.time.format.DateTimeFormatter;
35 import java.time.temporal.ChronoUnit;
36 import java.util.Locale;
37 import java.util.concurrent.TimeUnit;
38
39
40
41
42 final class KeytoolSelfSignedCertGenerator {
43 private static final DateTimeFormatter DATE_FORMAT =
44 DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss", Locale.ROOT);
45 private static final String ALIAS = "alias";
46 private static final String PASSWORD = "insecurepassword";
47 private static final Path KEYTOOL;
48 private static final String KEY_STORE_TYPE;
49
50 static {
51 String home = System.getProperty("java.home");
52 if (home == null) {
53 KEYTOOL = null;
54 } else {
55 Path likely = Paths.get(home).resolve("bin").resolve("keytool");
56 if (Files.exists(likely)) {
57 KEYTOOL = likely;
58 } else {
59 KEYTOOL = null;
60 }
61 }
62
63
64 KEY_STORE_TYPE = PlatformDependent.javaVersion() >= 11 ? "PKCS12" : "JKS";
65 }
66
67 private KeytoolSelfSignedCertGenerator() {
68 }
69
70 static boolean isAvailable() {
71 return KEYTOOL != null;
72 }
73
74 static void generate(SelfSignedCertificate.Builder builder) throws IOException, GeneralSecurityException {
75
76 String dirFqdn = builder.fqdn.replaceAll("[^\\w.-]", "x");
77
78 Path directory = Files.createTempDirectory("keytool_" + dirFqdn);
79 Path keyStore = directory.resolve("keystore.jks");
80 try {
81 Process process = new ProcessBuilder()
82 .command(
83 "keytool",
84 "-genkeypair",
85 "-keyalg", builder.algorithm,
86 "-keysize", String.valueOf(builder.bits),
87 "-startdate", DATE_FORMAT.format(
88 builder.notBefore.toInstant().atZone(ZoneId.systemDefault())),
89 "-validity", String.valueOf(builder.notBefore.toInstant().until(
90 builder.notAfter.toInstant(), ChronoUnit.DAYS)),
91 "-keystore", keyStore.toString(),
92 "-alias", ALIAS,
93 "-keypass", PASSWORD,
94 "-storepass", PASSWORD,
95 "-dname", "CN=" + builder.fqdn,
96 "-storetype", KEY_STORE_TYPE
97 )
98 .redirectErrorStream(true)
99 .start();
100 try {
101 if (!process.waitFor(60, TimeUnit.SECONDS)) {
102 process.destroyForcibly();
103 throw new IOException("keytool timeout");
104 }
105 } catch (InterruptedException e) {
106 process.destroyForcibly();
107 Thread.currentThread().interrupt();
108 throw new InterruptedIOException();
109 }
110
111 if (process.exitValue() != 0) {
112 ByteBuf buffer = Unpooled.buffer();
113 try {
114 try (InputStream stream = process.getInputStream()) {
115 while (true) {
116 if (buffer.writeBytes(stream, 4096) == -1) {
117 break;
118 }
119 }
120 }
121 String log = buffer.toString(StandardCharsets.UTF_8);
122 throw new IOException("Keytool exited with status " + process.exitValue() + ": " + log);
123 } finally {
124 buffer.release();
125 }
126 }
127
128 KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE);
129 try (InputStream is = Files.newInputStream(keyStore)) {
130 ks.load(is, PASSWORD.toCharArray());
131 }
132 KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) ks.getEntry(
133 ALIAS, new KeyStore.PasswordProtection(PASSWORD.toCharArray()));
134 builder.paths = SelfSignedCertificate.newSelfSignedCertificate(
135 builder.fqdn, entry.getPrivateKey(), (X509Certificate) entry.getCertificate());
136 builder.privateKey = entry.getPrivateKey();
137 } finally {
138 Files.deleteIfExists(keyStore);
139 Files.delete(directory);
140 }
141 }
142 }