View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2012-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.jcabi.ssl.maven.plugin;
6   
7   import com.jcabi.aspects.Immutable;
8   import com.jcabi.aspects.Loggable;
9   import com.jcabi.log.Logger;
10  import com.jcabi.log.VerboseProcess;
11  import java.io.File;
12  import java.io.IOException;
13  import java.io.OutputStreamWriter;
14  import java.io.PrintWriter;
15  import java.nio.charset.StandardCharsets;
16  import java.util.ArrayList;
17  import java.util.List;
18  import java.util.Locale;
19  import java.util.logging.Level;
20  import lombok.EqualsAndHashCode;
21  import lombok.ToString;
22  import org.apache.commons.io.FileUtils;
23  
24  /**
25   * Keytool abstraction.
26   *
27   * @since 0.5
28   */
29  @Immutable
30  @ToString
31  @EqualsAndHashCode(of = { "keystore", "password" })
32  final class Keytool {
33      /**
34       * Localhost, input to the keytool.
35       */
36      private static final String LOCALHOST = "localhost";
37  
38      /**
39       * Platform-dependent line separator.
40       */
41      private static final String NEWLINE = System.getProperty("line.separator");
42  
43      /**
44       * Keystore location.
45       */
46      private final transient String keystore;
47  
48      /**
49       * Keystore password.
50       */
51      private final transient String password;
52  
53      /**
54       * Ctor.
55       * @param store The location of keystore
56       * @param pwd The password
57       */
58      Keytool(final File store, final String pwd) {
59          this.keystore = store.getAbsolutePath();
60          this.password = pwd;
61      }
62  
63      /**
64       * List content of the keystore.
65       * @return The content of it
66       * @throws IOException If fails
67       */
68      @Loggable(Loggable.DEBUG)
69      public String list() throws IOException {
70          final List<String> cmds = new ArrayList<>(10);
71          cmds.add(Keytool.keytool());
72          cmds.add("-list");
73          cmds.add("-v");
74          cmds.add("-keystore");
75          cmds.add(this.keystore);
76          cmds.add("-storepass");
77          cmds.add(this.password);
78          return new VerboseProcess(
79              Keytool.utf(new ProcessBuilder(cmds)), Level.FINE, Level.FINE
80          ).stdout();
81      }
82  
83      /**
84       * Generate key.
85       * @throws IOException If fails
86       */
87      @Loggable(Loggable.DEBUG)
88      public void genkey() throws IOException {
89          final Process proc = Keytool.utf(
90              this.proc(
91                  "-genkeypair",
92                  "-alias",
93                  Keytool.LOCALHOST,
94                  "-keyalg",
95                  "RSA",
96                  "-keysize",
97                  "2048",
98                  "-keypass",
99                  this.password
100             )
101         ).start();
102         try (PrintWriter writer = new PrintWriter(
103             new OutputStreamWriter(proc.getOutputStream(), StandardCharsets.UTF_8)
104         )) {
105             writer.print(Keytool.appendNewLine(Keytool.LOCALHOST));
106             writer.print(Keytool.appendNewLine("ACME Co."));
107             writer.print(Keytool.appendNewLine("software developers"));
108             writer.print(Keytool.appendNewLine("San Francisco"));
109             writer.print(Keytool.appendNewLine("California"));
110             writer.print(Keytool.appendNewLine("US"));
111             writer.print(Keytool.appendNewLine(Keytool.localeDependentYes()));
112         }
113         new VerboseProcess(proc, Level.FINE, Level.FINE).stdout();
114         Logger.info(
115             this,
116             "Keystore created in '%s' (%s)",
117             this.keystore,
118             FileUtils.byteCountToDisplaySize(this.keystore.length())
119         );
120     }
121 
122     /**
123      * Import certificate into this store.
124      * @param file The file to import
125      * @param pwd The password there
126      * @throws IOException If fails
127      */
128     @Loggable(Loggable.DEBUG)
129     public void imprt(final File file, final String pwd) throws IOException {
130         final List<String> cmds = new ArrayList<>(20);
131         cmds.add(Keytool.keytool());
132         cmds.add("-importkeystore");
133         cmds.add("-srckeystore");
134         cmds.add(file.getAbsolutePath());
135         cmds.add("-srcstorepass");
136         cmds.add(pwd);
137         cmds.add("-srcalias");
138         cmds.add(Keytool.LOCALHOST);
139         cmds.add("-srckeypass");
140         cmds.add(pwd);
141         cmds.add("-srcstoretype");
142         cmds.add("jks");
143         cmds.add("-destkeystore");
144         cmds.add(this.keystore);
145         cmds.add("-deststorepass");
146         cmds.add(this.password);
147         cmds.add("-destkeypass");
148         cmds.add(this.password);
149         cmds.add("-deststoretype");
150         cmds.add("jks");
151         cmds.add("-noprompt");
152         new VerboseProcess(
153             Keytool.utf(new ProcessBuilder(cmds)), Level.FINE, Level.FINE
154         ).stdout();
155     }
156 
157     /**
158      * Creates a string, which consists of string with an appended
159      * platform-dependent line separator.
160      * @param text Text, to which the line separator needs to be appended
161      * @return Contents of text with appended line separator
162      */
163     private static String appendNewLine(final String text) {
164         return String.format("%s%s", text, Keytool.NEWLINE);
165     }
166 
167     /**
168      * Creates a text, which represents "yes" in the language,
169      * specified by the current locale.
170      * @return The word "Yes" translated to the current language
171      */
172     private static String localeDependentYes() {
173         return new Yes().translate(Locale.getDefault());
174     }
175 
176     /**
177      * Returns the path to the keytool executable.
178      * @return Path to keytool
179      */
180     private static String keytool() {
181         return String.format(
182             "%s/bin/keytool",
183             System.getProperty("java.home")
184         );
185     }
186 
187     /**
188      * Create process builder.
189      * @param args Arguments
190      * @return Process just created and started
191      */
192     private ProcessBuilder proc(final String... args) {
193         final List<String> cmds = new ArrayList<>(args.length + 1);
194         cmds.add(Keytool.keytool());
195         cmds.addAll(java.util.Arrays.asList(args));
196         cmds.add("-storetype");
197         cmds.add("jks");
198         cmds.add("-noprompt");
199         cmds.add("-storepass");
200         cmds.add(this.password);
201         cmds.add("-keystore");
202         cmds.add(this.keystore);
203         return new ProcessBuilder(cmds);
204     }
205 
206     /**
207      * Configure process builder to use UTF-8 encoding.
208      * @param builder The process builder
209      * @return The same builder with UTF-8 environment
210      */
211     private static ProcessBuilder utf(final ProcessBuilder builder) {
212         builder.environment().put(
213             "JAVA_TOOL_OPTIONS",
214             "-Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8"
215         );
216         return builder;
217     }
218 }