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.toAbsolutePath().toString(),
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 }