View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.ssl.util;
18  
19  import io.netty.util.internal.PlatformDependent;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  
23  import java.lang.invoke.MethodHandle;
24  import java.lang.invoke.MethodHandles;
25  import java.math.BigInteger;
26  import java.security.AccessController;
27  import java.security.KeyPair;
28  import java.security.PrivateKey;
29  import java.security.PrivilegedAction;
30  import java.security.PublicKey;
31  import java.security.SecureRandom;
32  import java.security.cert.CertificateException;
33  import java.security.cert.X509Certificate;
34  import java.util.ArrayList;
35  import java.util.Date;
36  import java.util.List;
37  
38  import static io.netty.handler.ssl.util.SelfSignedCertificate.newSelfSignedCertificate;
39  import static java.lang.invoke.MethodType.methodType;
40  
41  /**
42   * Generates a self-signed certificate using {@code sun.security.x509} package provided by OpenJDK.
43   */
44  final class OpenJdkSelfSignedCertGenerator {
45      private static final InternalLogger logger =
46              InternalLoggerFactory.getInstance(OpenJdkSelfSignedCertGenerator.class);
47      private static final MethodHandle CERT_INFO_SET_HANDLE;
48      private static final MethodHandle ISSUER_NAME_CONSTRUCTOR;
49      private static final MethodHandle CERT_IMPL_CONSTRUCTOR;
50      private static final MethodHandle X509_CERT_INFO_CONSTRUCTOR;
51      private static final MethodHandle CERTIFICATE_VERSION_CONSTRUCTOR;
52      private static final MethodHandle CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR;
53      private static final MethodHandle X500_NAME_CONSTRUCTOR;
54      private static final MethodHandle CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR;
55      private static final MethodHandle CERTIFICATE_VALIDITY_CONSTRUCTOR;
56      private static final MethodHandle CERTIFICATE_X509_KEY_CONSTRUCTOR;
57      private static final MethodHandle CERTIFICATE_ALORITHM_ID_CONSTRUCTOR;
58      private static final MethodHandle CERT_IMPL_GET_HANDLE;
59      private static final MethodHandle CERT_IMPL_SIGN_HANDLE;
60      private static final MethodHandle ALGORITHM_ID_GET_HANDLE;
61  
62      private static final boolean SUPPORTED;
63  
64      // Use reflection as JDK20+ did change things quite a bit.
65      static {
66          final MethodHandles.Lookup lookup = MethodHandles.lookup();
67  
68          final Class<?> x509CertInfoClass;
69          final Class<?> x500NameClass;
70          final Class<?> certificateIssuerNameClass;
71          final Class<?> x509CertImplClass;
72          final Class<?> certificateVersionClass;
73          final Class<?> certificateSubjectNameClass;
74          final Class<?> certificateSerialNumberClass;
75          final Class<?> certificateValidityClass;
76          final Class<?> certificateX509KeyClass;
77          final Class<?> algorithmIdClass;
78          final Class<?> certificateAlgorithmIdClass;
79  
80          boolean supported;
81          MethodHandle certInfoSetHandle = null;
82          MethodHandle x509CertInfoConstructor = null;
83          MethodHandle issuerNameConstructor = null;
84          MethodHandle certImplConstructor = null;
85          MethodHandle x500NameConstructor = null;
86          MethodHandle certificateVersionConstructor = null;
87          MethodHandle certificateSubjectNameConstructor = null;
88          MethodHandle certificateSerialNumberConstructor = null;
89          MethodHandle certificateValidityConstructor = null;
90          MethodHandle certificateX509KeyConstructor = null;
91          MethodHandle certificateAlgorithmIdConstructor = null;
92          MethodHandle certImplGetHandle = null;
93          MethodHandle certImplSignHandle = null;
94          MethodHandle algorithmIdGetHandle = null;
95  
96          try {
97              Object maybeClasses = AccessController.doPrivileged(new PrivilegedAction<Object>() {
98                  @Override
99                  public Object run() {
100                     try {
101                         List<Class<?>> classes = new ArrayList<>();
102                         classes.add(Class.forName("sun.security.x509.X509CertInfo", false,
103                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
104                         classes.add(Class.forName("sun.security.x509.X500Name", false,
105                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
106                         classes.add(Class.forName("sun.security.x509.CertificateIssuerName", false,
107                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
108                         classes.add(Class.forName("sun.security.x509.X509CertImpl", false,
109                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
110                         classes.add(Class.forName("sun.security.x509.CertificateVersion", false,
111                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
112                         classes.add(Class.forName("sun.security.x509.CertificateSubjectName", false,
113                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
114                         classes.add(Class.forName("sun.security.x509.CertificateSerialNumber", false,
115                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
116                         classes.add(Class.forName("sun.security.x509.CertificateValidity", false,
117                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
118                         classes.add(Class.forName("sun.security.x509.CertificateX509Key", false,
119                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
120                         classes.add(Class.forName("sun.security.x509.AlgorithmId", false,
121                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
122                         classes.add(Class.forName("sun.security.x509.CertificateAlgorithmId", false,
123                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
124 
125                         return classes;
126                     } catch (Throwable cause) {
127                         return cause;
128                     }
129                 }
130             });
131             if (maybeClasses instanceof List) {
132                 @SuppressWarnings("unchecked") List<Class<?>> classes = (List<Class<?>>) maybeClasses;
133                 x509CertInfoClass = classes.get(0);
134                 x500NameClass = classes.get(1);
135                 certificateIssuerNameClass = classes.get(2);
136                 x509CertImplClass = classes.get(3);
137                 certificateVersionClass = classes.get(4);
138                 certificateSubjectNameClass = classes.get(5);
139                 certificateSerialNumberClass = classes.get(6);
140                 certificateValidityClass = classes.get(7);
141                 certificateX509KeyClass = classes.get(8);
142                 algorithmIdClass = classes.get(9);
143                 certificateAlgorithmIdClass = classes.get(10);
144             } else {
145                 throw (Throwable) maybeClasses;
146             }
147 
148             Object maybeConstructors = AccessController.doPrivileged(new PrivilegedAction<Object>() {
149                 @Override
150                 public Object run() {
151                     try {
152                         List<MethodHandle> constructors = new ArrayList<>();
153                         constructors.add(
154                                 lookup.unreflectConstructor(x509CertInfoClass.getConstructor())
155                                         .asType(methodType(x509CertInfoClass))
156                         );
157                         constructors.add(
158                                 lookup.unreflectConstructor(certificateIssuerNameClass.getConstructor(x500NameClass))
159                                         .asType(methodType(certificateIssuerNameClass, x500NameClass))
160                         );
161                         constructors.add(
162                                 lookup.unreflectConstructor(x509CertImplClass.getConstructor(x509CertInfoClass))
163                                         .asType(methodType(x509CertImplClass, x509CertInfoClass))
164                         );
165                         constructors.add(
166                                 lookup.unreflectConstructor(x500NameClass.getConstructor(String.class))
167                                         .asType(methodType(x500NameClass, String.class))
168                         );
169                         constructors.add(
170                                 lookup.unreflectConstructor(certificateVersionClass.getConstructor(int.class))
171                                         .asType(methodType(certificateVersionClass, int.class))
172                         );
173                         constructors.add(
174                                 lookup.unreflectConstructor(certificateSubjectNameClass.getConstructor(x500NameClass))
175                                         .asType(methodType(certificateSubjectNameClass, x500NameClass))
176                         );
177                         constructors.add(
178                                 lookup.unreflectConstructor(
179                                         certificateSerialNumberClass.getConstructor(BigInteger.class))
180                                         .asType(methodType(certificateSerialNumberClass, BigInteger.class))
181                         );
182                         constructors.add(
183                                 lookup.unreflectConstructor(
184                                         certificateValidityClass.getConstructor(Date.class, Date.class))
185                                         .asType(methodType(certificateValidityClass, Date.class, Date.class))
186                         );
187                         constructors.add(
188                                 lookup.unreflectConstructor(certificateX509KeyClass.getConstructor(PublicKey.class))
189                                         .asType(methodType(certificateX509KeyClass, PublicKey.class))
190                         );
191 
192                         constructors.add(
193                                 lookup.unreflectConstructor(
194                                         certificateAlgorithmIdClass.getConstructor(algorithmIdClass))
195                                         .asType(methodType(certificateAlgorithmIdClass, algorithmIdClass))
196                         );
197                         return constructors;
198                     } catch (Throwable cause) {
199                         return cause;
200                     }
201                 }
202             });
203             if (maybeConstructors instanceof List) {
204                 @SuppressWarnings("unchecked") List<MethodHandle> constructorList =
205                         (List<MethodHandle>) maybeConstructors;
206                 x509CertInfoConstructor = constructorList.get(0);
207                 issuerNameConstructor = constructorList.get(1);
208                 certImplConstructor = constructorList.get(2);
209                 x500NameConstructor = constructorList.get(3);
210                 certificateVersionConstructor = constructorList.get(4);
211                 certificateSubjectNameConstructor = constructorList.get(5);
212                 certificateSerialNumberConstructor = constructorList.get(6);
213                 certificateValidityConstructor = constructorList.get(7);
214                 certificateX509KeyConstructor = constructorList.get(8);
215                 certificateAlgorithmIdConstructor = constructorList.get(9);
216             } else {
217                 throw (Throwable) maybeConstructors;
218             }
219 
220             Object maybeMethodHandles = AccessController.doPrivileged(new PrivilegedAction<Object>() {
221                 @Override
222                 public Object run() {
223                     try {
224                         List<MethodHandle> methods = new ArrayList<>();
225                         methods.add(
226                                 lookup.findVirtual(x509CertInfoClass, "set",
227                                         methodType(void.class, String.class, Object.class))
228                         );
229                         methods.add(
230                                 lookup.findVirtual(x509CertImplClass, "get",
231                                         methodType(Object.class, String.class))
232                         );
233 
234                         methods.add(
235                                 lookup.findVirtual(x509CertImplClass, "sign",
236                                         methodType(void.class, PrivateKey.class, String.class))
237                         );
238                         methods.add(
239                                 lookup.findStatic(algorithmIdClass, "get",
240                                         methodType(algorithmIdClass, String.class))
241                         );
242                         return methods;
243                     } catch (Throwable cause) {
244                         return cause;
245                     }
246                 }
247             });
248             if (maybeMethodHandles instanceof List) {
249                 @SuppressWarnings("unchecked") List<MethodHandle> methodHandles =
250                         (List<MethodHandle>) maybeMethodHandles;
251                 certInfoSetHandle = methodHandles.get(0);
252                 certImplGetHandle = methodHandles.get(1);
253                 certImplSignHandle = methodHandles.get(2);
254                 algorithmIdGetHandle = methodHandles.get(3);
255             } else {
256                 throw (Throwable) maybeMethodHandles;
257             }
258             supported = true;
259         } catch (Throwable cause) {
260             supported = false;
261             logger.debug(OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported", cause);
262         }
263         CERT_INFO_SET_HANDLE = certInfoSetHandle;
264         X509_CERT_INFO_CONSTRUCTOR = x509CertInfoConstructor;
265         ISSUER_NAME_CONSTRUCTOR = issuerNameConstructor;
266         CERTIFICATE_VERSION_CONSTRUCTOR = certificateVersionConstructor;
267         CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR = certificateSubjectNameConstructor;
268         CERT_IMPL_CONSTRUCTOR = certImplConstructor;
269         X500_NAME_CONSTRUCTOR = x500NameConstructor;
270         CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR = certificateSerialNumberConstructor;
271         CERTIFICATE_VALIDITY_CONSTRUCTOR = certificateValidityConstructor;
272         CERTIFICATE_X509_KEY_CONSTRUCTOR = certificateX509KeyConstructor;
273         CERT_IMPL_GET_HANDLE = certImplGetHandle;
274         CERT_IMPL_SIGN_HANDLE = certImplSignHandle;
275         ALGORITHM_ID_GET_HANDLE = algorithmIdGetHandle;
276         CERTIFICATE_ALORITHM_ID_CONSTRUCTOR = certificateAlgorithmIdConstructor;
277         SUPPORTED = supported;
278     }
279 
280     static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
281                              String algorithm) throws Exception {
282         if (!SUPPORTED) {
283             throw new UnsupportedOperationException(
284                     OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported on the used JDK version");
285         }
286         try {
287             PrivateKey key = keypair.getPrivate();
288 
289             // Prepare the information required for generating an X.509 certificate.
290             Object info = X509_CERT_INFO_CONSTRUCTOR.invoke();
291             Object owner = X500_NAME_CONSTRUCTOR.invoke("CN=" + fqdn);
292 
293             CERT_INFO_SET_HANDLE.invoke(info, "version", CERTIFICATE_VERSION_CONSTRUCTOR.invoke(2));
294             CERT_INFO_SET_HANDLE.invoke(info, "serialNumber",
295                     CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR.invoke(new BigInteger(64, random)));
296             try {
297                 CERT_INFO_SET_HANDLE.invoke(info, "subject", CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR.invoke(owner));
298             } catch (CertificateException ex) {
299                 CERT_INFO_SET_HANDLE.invoke(info, "subject", owner);
300             }
301             try {
302                 CERT_INFO_SET_HANDLE.invoke(info, "issuer", ISSUER_NAME_CONSTRUCTOR.invoke(owner));
303             } catch (CertificateException ex) {
304                 CERT_INFO_SET_HANDLE.invoke(info, "issuer", owner);
305             }
306             CERT_INFO_SET_HANDLE.invoke(info, "validity",
307                     CERTIFICATE_VALIDITY_CONSTRUCTOR.invoke(notBefore, notAfter));
308             CERT_INFO_SET_HANDLE.invoke(info, "key", CERTIFICATE_X509_KEY_CONSTRUCTOR.invoke(keypair.getPublic()));
309             CERT_INFO_SET_HANDLE.invoke(info, "algorithmID",
310                     // sha256WithRSAEncryption
311                     CERTIFICATE_ALORITHM_ID_CONSTRUCTOR.invoke(
312                             ALGORITHM_ID_GET_HANDLE.invoke("1.2.840.113549.1.1.11")));
313 
314             // Sign the cert to identify the algorithm that's used.
315             Object cert = CERT_IMPL_CONSTRUCTOR.invoke(info);
316             CERT_IMPL_SIGN_HANDLE.invoke(cert, key,
317                     algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
318 
319             // Update the algorithm and sign again.
320             CERT_INFO_SET_HANDLE.invoke(info, "algorithmID.algorithm",
321                     CERT_IMPL_GET_HANDLE.invoke(cert, "x509.algorithm"));
322             cert = CERT_IMPL_CONSTRUCTOR.invoke(info);
323             CERT_IMPL_SIGN_HANDLE.invoke(cert, key,
324                     algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
325 
326             X509Certificate x509Cert = (X509Certificate) cert;
327             x509Cert.verify(keypair.getPublic());
328 
329             return newSelfSignedCertificate(fqdn, key, x509Cert);
330         } catch (Throwable cause) {
331             if (cause instanceof Exception) {
332                 throw (Exception) cause;
333             }
334             if (cause instanceof Error) {
335                 throw (Error) cause;
336             }
337             throw new IllegalStateException(cause);
338         }
339     }
340 
341     private OpenJdkSelfSignedCertGenerator() { }
342 }