1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.http;
20
21 import davmail.Settings;
22 import org.apache.log4j.Logger;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.lang.reflect.Constructor;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.net.InetAddress;
31 import java.net.UnknownHostException;
32 import java.nio.file.Files;
33 import java.nio.file.Paths;
34 import java.security.GeneralSecurityException;
35 import java.security.KeyPair;
36 import java.security.KeyPairGenerator;
37 import java.security.KeyStore;
38 import java.security.PrivateKey;
39 import java.security.PublicKey;
40 import java.security.SecureRandom;
41 import java.security.cert.X509Certificate;
42 import java.util.Date;
43
44
45
46
47
48
49
50
51
52
53 public class SelfSignedCertificateGenerator {
54 private static final Logger LOGGER = Logger.getLogger(SelfSignedCertificateGenerator.class);
55
56 private static final String CERTIFICATE_SIGNATURE_ALGORITHM = "SHA384withRSA";
57 private static final int RSA_KEY_LENGTH = 2048;
58 private static final int CERTIFICATE_VALIDITY_DAYS = 3650;
59 private static final String KEY_ALIAS = "davmail";
60 public static final String DEFAULT_KEYSTORE_PASSWORD = "password";
61 private static final String DEFAULT_KEYSTORE_FILENAME = "davmail.p12";
62
63 private SelfSignedCertificateGenerator() {
64
65 }
66
67
68
69
70
71
72
73 public static String resolveKeystorePath(String configFilePath) {
74 if (configFilePath != null && !configFilePath.isEmpty()) {
75 File configFile = new File(configFilePath);
76 File configDir = configFile.getParentFile();
77 if (configDir != null) {
78 return new File(configDir, DEFAULT_KEYSTORE_FILENAME).getPath();
79 }
80 }
81 return DEFAULT_KEYSTORE_FILENAME;
82 }
83
84
85
86
87
88
89
90
91 public static void generate(String configFilePath) {
92 String keystorePath = resolveKeystorePath(configFilePath);
93
94 try {
95 generateAndSave(keystorePath, DEFAULT_KEYSTORE_PASSWORD);
96
97
98 Settings.setProperty("davmail.ssl.keystoreType", "PKCS12");
99 Settings.setProperty("davmail.ssl.keystoreFile", keystorePath);
100 Settings.setProperty("davmail.ssl.keystorePass", DEFAULT_KEYSTORE_PASSWORD);
101 Settings.setProperty("davmail.ssl.keyPass", DEFAULT_KEYSTORE_PASSWORD);
102 Settings.save();
103
104 LOGGER.info("Generated self-signed certificate: " + keystorePath);
105 LOGGER.info("DavMail settings updated");
106 } catch (GeneralSecurityException | IOException e) {
107 LOGGER.error("Failed to generate certificate: " + e.getMessage(), e);
108 LOGGER.error("On Java 11+, add: --add-exports java.base/sun.security.x509=ALL-UNNAMED");
109 }
110 }
111
112
113
114
115
116
117
118
119
120 public static void generateAndSave(String keystoreFilename, String keystorePassword) throws GeneralSecurityException, IOException {
121 String hostname;
122 try {
123 hostname = InetAddress.getLocalHost().getCanonicalHostName();
124 } catch (UnknownHostException e) {
125 hostname = "localhost";
126 }
127 String cnString = "cn=" + hostname + ",ou=davmail";
128
129 LOGGER.info("Generating self-signed certificate for " + hostname);
130
131
132 SecureRandom secureRandom = new SecureRandom();
133 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
134 keyGen.initialize(RSA_KEY_LENGTH, secureRandom);
135 KeyPair pair = keyGen.generateKeyPair();
136 PublicKey publicKey = pair.getPublic();
137 PrivateKey privateKey = pair.getPrivate();
138
139 Date startDate = new Date();
140 Date endDate = new Date(startDate.getTime() + (long) CERTIFICATE_VALIDITY_DAYS * 24 * 3600 * 1000);
141
142
143 X509Certificate cert = createSelfSignedCertificate(
144 cnString, publicKey, privateKey, startDate, endDate, secureRandom);
145
146
147 KeyStore keyStore = KeyStore.getInstance("PKCS12");
148 keyStore.load(null, null);
149 keyStore.setKeyEntry(KEY_ALIAS, privateKey, keystorePassword.toCharArray(),
150 new X509Certificate[]{cert});
151
152 try (OutputStream os = Files.newOutputStream(Paths.get(keystoreFilename))) {
153 keyStore.store(os, keystorePassword.toCharArray());
154 }
155
156 LOGGER.info("Self-signed certificate saved to " + keystoreFilename);
157 }
158
159 private static X509Certificate createSelfSignedCertificate(
160 String cnString, PublicKey publicKey, PrivateKey privateKey,
161 Date startDate, Date endDate, SecureRandom secureRandom) throws GeneralSecurityException {
162 try {
163
164 Class<?> x509CertInfoClass = Class.forName("sun.security.x509.X509CertInfo");
165 Class<?> x509CertImplClass = Class.forName("sun.security.x509.X509CertImpl");
166 Class<?> certificateVersionClass = Class.forName("sun.security.x509.CertificateVersion");
167 Class<?> certificateSerialNumberClass = Class.forName("sun.security.x509.CertificateSerialNumber");
168 Class<?> algorithmIdClass = Class.forName("sun.security.x509.AlgorithmId");
169 Class<?> certificateAlgorithmIdClass = Class.forName("sun.security.x509.CertificateAlgorithmId");
170 Class<?> x500NameClass = Class.forName("sun.security.x509.X500Name");
171 Class<?> certificateX509KeyClass = Class.forName("sun.security.x509.CertificateX509Key");
172 Class<?> certificateValidityClass = Class.forName("sun.security.x509.CertificateValidity");
173
174
175 Object info = x509CertInfoClass.getDeclaredConstructor().newInstance();
176
177
178 Object certVersion = certificateVersionClass.getDeclaredConstructor(int.class).newInstance(2);
179
180
181 Object certSerial = certificateSerialNumberClass.getDeclaredConstructor(int.class)
182 .newInstance(secureRandom.nextInt() & 0x7fffffff);
183
184
185 Method algorithmIdGet = algorithmIdClass.getMethod("get", String.class);
186 Object algId = algorithmIdGet.invoke(null, CERTIFICATE_SIGNATURE_ALGORITHM);
187 Object certAlgId = certificateAlgorithmIdClass.getDeclaredConstructor(algorithmIdClass).newInstance(algId);
188
189
190 Object x500Name = x500NameClass.getDeclaredConstructor(String.class).newInstance(cnString);
191
192
193 Object certKey = certificateX509KeyClass.getDeclaredConstructor(PublicKey.class).newInstance(publicKey);
194
195
196 Object certValidity = certificateValidityClass.getDeclaredConstructor(Date.class, Date.class)
197 .newInstance(startDate, endDate);
198
199
200 setInfoField(info, x509CertInfoClass, "version", "setVersion", certVersion, certificateVersionClass);
201 setInfoField(info, x509CertInfoClass, "serialNumber", "setSerialNumber", certSerial, certificateSerialNumberClass);
202 setInfoField(info, x509CertInfoClass, "algorithmId", "setAlgorithmId", certAlgId, certificateAlgorithmIdClass);
203 setInfoField(info, x509CertInfoClass, "key", "setKey", certKey, certificateX509KeyClass);
204 setInfoField(info, x509CertInfoClass, "validity", "setValidity", certValidity, certificateValidityClass);
205
206
207 setSubjectOrIssuer(info, x509CertInfoClass, "subject", "setSubject", x500Name, x500NameClass);
208 setSubjectOrIssuer(info, x509CertInfoClass, "issuer", "setIssuer", x500Name, x500NameClass);
209
210
211 return signCertificate(info, x509CertInfoClass, x509CertImplClass, privateKey);
212 } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
213 IllegalAccessException | InvocationTargetException e) {
214 throw new GeneralSecurityException("Failed to generate self-signed certificate: " + e.getMessage(), e);
215 }
216 }
217
218
219
220
221
222 private static void setInfoField(Object info, Class<?> infoClass, String fieldName,
223 String setterName, Object value, Class<?> valueClass)
224 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
225 try {
226
227 Method setter = infoClass.getMethod(setterName, valueClass);
228 setter.invoke(info, value);
229 } catch (NoSuchMethodException e) {
230
231 Method setMethod = infoClass.getMethod("set", String.class, Object.class);
232 setMethod.invoke(info, fieldName, value);
233 }
234 }
235
236
237
238
239
240
241 private static void setSubjectOrIssuer(Object info, Class<?> infoClass, String fieldName,
242 String setterName, Object x500Name, Class<?> x500NameClass)
243 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
244 ClassNotFoundException, InstantiationException {
245 try {
246
247 Method setter = infoClass.getMethod(setterName, x500NameClass);
248 setter.invoke(info, x500Name);
249 } catch (NoSuchMethodException e) {
250
251 try {
252 String wrapperClassName;
253 if ("subject".equals(fieldName)) {
254 wrapperClassName = "sun.security.x509.CertificateSubjectName";
255 } else {
256 wrapperClassName = "sun.security.x509.CertificateIssuerName";
257 }
258 Class<?> wrapperClass = Class.forName(wrapperClassName);
259 Object wrapper = wrapperClass.getDeclaredConstructor(x500NameClass).newInstance(x500Name);
260 Method setMethod = infoClass.getMethod("set", String.class, Object.class);
261 setMethod.invoke(info, fieldName, wrapper);
262 } catch (ClassNotFoundException cnfe) {
263
264 Method setMethod = infoClass.getMethod("set", String.class, Object.class);
265 setMethod.invoke(info, fieldName, x500Name);
266 }
267 }
268 }
269
270
271
272
273
274 private static X509Certificate signCertificate(Object info, Class<?> infoClass,
275 Class<?> certImplClass, PrivateKey privateKey)
276 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
277 try {
278
279 Method newSigned = certImplClass.getMethod("newSigned", infoClass, PrivateKey.class, String.class);
280 return (X509Certificate) newSigned.invoke(null, info, privateKey, CERTIFICATE_SIGNATURE_ALGORITHM);
281 } catch (NoSuchMethodException e) {
282
283 Constructor<?> constructor = certImplClass.getDeclaredConstructor(infoClass);
284 Object cert = constructor.newInstance(info);
285 Method signMethod = certImplClass.getMethod("sign", PrivateKey.class, String.class);
286 signMethod.invoke(cert, privateKey, CERTIFICATE_SIGNATURE_ALGORITHM);
287 return (X509Certificate) cert;
288 }
289 }
290 }