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.reflect.Constructor;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.math.BigInteger;
27  import java.security.AccessController;
28  import java.security.KeyPair;
29  import java.security.PrivateKey;
30  import java.security.PrivilegedAction;
31  import java.security.PublicKey;
32  import java.security.SecureRandom;
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  
40  /**
41   * Generates a self-signed certificate using {@code sun.security.x509} package provided by OpenJDK.
42   */
43  final class OpenJdkSelfSignedCertGenerator {
44      private static final InternalLogger logger =
45              InternalLoggerFactory.getInstance(OpenJdkSelfSignedCertGenerator.class);
46      private static final Method CERT_INFO_SET_HANDLE;
47      private static final Constructor<?> ISSUER_NAME_CONSTRUCTOR;
48      private static final Constructor<?> CERT_IMPL_CONSTRUCTOR;
49      private static final Constructor<?> X509_CERT_INFO_CONSTRUCTOR;
50      private static final Constructor<?> CERTIFICATE_VERSION_CONSTRUCTOR;
51      private static final Constructor<?> CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR;
52      private static final Constructor<?> X500_NAME_CONSTRUCTOR;
53      private static final Constructor<?> CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR;
54      private static final Constructor<?> CERTIFICATE_VALIDITY_CONSTRUCTOR;
55      private static final Constructor<?> CERTIFICATE_X509_KEY_CONSTRUCTOR;
56      private static final Constructor<?> CERTIFICATE_ALORITHM_ID_CONSTRUCTOR;
57      private static final Method CERT_IMPL_GET_HANDLE;
58      private static final Method CERT_IMPL_SIGN_HANDLE;
59      private static final Method ALGORITHM_ID_GET_HANDLE;
60  
61      private static final boolean SUPPORTED;
62  
63      static {
64          final Class<?> x509CertInfoClass;
65          final Class<?> x500NameClass;
66          final Class<?> certificateIssuerNameClass;
67          final Class<?> x509CertImplClass;
68          final Class<?> certificateVersionClass;
69          final Class<?> certificateSubjectNameClass;
70          final Class<?> certificateSerialNumberClass;
71          final Class<?> certificateValidityClass;
72          final Class<?> certificateX509KeyClass;
73          final Class<?> algorithmIdClass;
74          final Class<?> certificateAlgorithmIdClass;
75  
76          boolean supported;
77          Method certInfoSetHandle = null;
78          Constructor<?> x509CertInfoConstructor = null;
79          Constructor<?> issuerNameConstructor = null;
80          Constructor<?> certImplConstructor = null;
81          Constructor<?> x500NameConstructor = null;
82          Constructor<?> certificateVersionConstructor = null;
83          Constructor<?> certificateSubjectNameConstructor = null;
84          Constructor<?> certificateSerialNumberConstructor = null;
85          Constructor<?> certificateValidityConstructor = null;
86          Constructor<?> certificateX509KeyConstructor = null;
87          Constructor<?> certificateAlgorithmIdConstructor = null;
88          Method certImplGetHandle = null;
89          Method certImplSignHandle = null;
90          Method algorithmIdGetHandle = null;
91  
92          try {
93              Object maybeClasses = AccessController.doPrivileged(new PrivilegedAction<Object>() {
94                  @Override
95                  public Object run() {
96                      try {
97                          List<Class<?>> classes = new ArrayList<Class<?>>();
98                          classes.add(Class.forName("sun.security.x509.X509CertInfo", false,
99                                  PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
100                         classes.add(Class.forName("sun.security.x509.X500Name", false,
101                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
102                         classes.add(Class.forName("sun.security.x509.CertificateIssuerName", false,
103                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
104                         classes.add(Class.forName("sun.security.x509.X509CertImpl", false,
105                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
106                         classes.add(Class.forName("sun.security.x509.CertificateVersion", false,
107                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
108                         classes.add(Class.forName("sun.security.x509.CertificateSubjectName", false,
109                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
110                         classes.add(Class.forName("sun.security.x509.CertificateSerialNumber", false,
111                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
112                         classes.add(Class.forName("sun.security.x509.CertificateValidity", false,
113                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
114                         classes.add(Class.forName("sun.security.x509.CertificateX509Key", false,
115                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
116                         classes.add(Class.forName("sun.security.x509.AlgorithmId", false,
117                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
118                         classes.add(Class.forName("sun.security.x509.CertificateAlgorithmId", false,
119                                 PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class)));
120 
121                         return classes;
122                     } catch (Throwable cause) {
123                         return cause;
124                     }
125                 }
126             });
127             if (maybeClasses instanceof List) {
128                 @SuppressWarnings("unchecked") List<Class<?>> classes = (List<Class<?>>) maybeClasses;
129                 x509CertInfoClass = classes.get(0);
130                 x500NameClass = classes.get(1);
131                 certificateIssuerNameClass = classes.get(2);
132                 x509CertImplClass = classes.get(3);
133                 certificateVersionClass = classes.get(4);
134                 certificateSubjectNameClass = classes.get(5);
135                 certificateSerialNumberClass = classes.get(6);
136                 certificateValidityClass = classes.get(7);
137                 certificateX509KeyClass = classes.get(8);
138                 algorithmIdClass = classes.get(9);
139                 certificateAlgorithmIdClass = classes.get(10);
140             } else {
141                 throw (Throwable) maybeClasses;
142             }
143 
144             Object maybeConstructors = AccessController.doPrivileged(new PrivilegedAction<Object>() {
145                 @Override
146                 public Object run() {
147                     try {
148                         List<Constructor<?>> constructors = new ArrayList<Constructor<?>>();
149                         constructors.add(
150                                 x509CertInfoClass.getConstructor()
151                         );
152                         constructors.add(certificateIssuerNameClass.getConstructor(x500NameClass));
153                         constructors.add(x509CertImplClass.getConstructor(x509CertInfoClass));
154                         constructors.add(x500NameClass.getConstructor(String.class));
155                         constructors.add(certificateVersionClass.getConstructor(int.class));
156                         constructors.add(certificateSubjectNameClass.getConstructor(x500NameClass));
157                         constructors.add(certificateSerialNumberClass.getConstructor(BigInteger.class));
158                         constructors.add(certificateValidityClass.getConstructor(Date.class, Date.class));
159                         constructors.add(certificateX509KeyClass.getConstructor(PublicKey.class));
160                         constructors.add(certificateAlgorithmIdClass.getConstructor(algorithmIdClass));
161                         return constructors;
162                     } catch (Throwable cause) {
163                         return cause;
164                     }
165                 }
166             });
167             if (maybeConstructors instanceof List) {
168                 @SuppressWarnings("unchecked")
169                 List<Constructor<?>> constructorList = (List<Constructor<?>>) maybeConstructors;
170                 x509CertInfoConstructor = constructorList.get(0);
171                 issuerNameConstructor = constructorList.get(1);
172                 certImplConstructor = constructorList.get(2);
173                 x500NameConstructor = constructorList.get(3);
174                 certificateVersionConstructor = constructorList.get(4);
175                 certificateSubjectNameConstructor = constructorList.get(5);
176                 certificateSerialNumberConstructor = constructorList.get(6);
177                 certificateValidityConstructor = constructorList.get(7);
178                 certificateX509KeyConstructor = constructorList.get(8);
179                 certificateAlgorithmIdConstructor = constructorList.get(9);
180             } else {
181                 throw (Throwable) maybeConstructors;
182             }
183 
184             Object maybeMethods = AccessController.doPrivileged(new PrivilegedAction<Object>() {
185                 @Override
186                 public Object run() {
187                     try {
188                         List<Method> methods = new ArrayList<Method>();
189                         methods.add(x509CertInfoClass.getDeclaredMethod("set", String.class, Object.class));
190                         methods.add(x509CertImplClass.getDeclaredMethod("get", String.class));
191 
192                         methods.add(x509CertImplClass.getDeclaredMethod(
193                                 "sign", PrivateKey.class, String.class));
194                         methods.add(algorithmIdClass.getDeclaredMethod("get", String.class));
195                         return methods;
196                     } catch (Throwable cause) {
197                         return cause;
198                     }
199                 }
200             });
201             if (maybeMethods instanceof List) {
202                 @SuppressWarnings("unchecked") List<Method> methodHandles = (List<Method>) maybeMethods;
203                 certInfoSetHandle = methodHandles.get(0);
204                 certImplGetHandle = methodHandles.get(1);
205                 certImplSignHandle = methodHandles.get(2);
206                 algorithmIdGetHandle = methodHandles.get(3);
207             } else {
208                 throw (Throwable) maybeMethods;
209             }
210             supported = true;
211         } catch (Throwable cause) {
212             supported = false;
213             logger.debug(OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported", cause);
214         }
215         CERT_INFO_SET_HANDLE = certInfoSetHandle;
216         X509_CERT_INFO_CONSTRUCTOR = x509CertInfoConstructor;
217         ISSUER_NAME_CONSTRUCTOR = issuerNameConstructor;
218         CERTIFICATE_VERSION_CONSTRUCTOR = certificateVersionConstructor;
219         CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR = certificateSubjectNameConstructor;
220         CERT_IMPL_CONSTRUCTOR = certImplConstructor;
221         X500_NAME_CONSTRUCTOR = x500NameConstructor;
222         CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR = certificateSerialNumberConstructor;
223         CERTIFICATE_VALIDITY_CONSTRUCTOR = certificateValidityConstructor;
224         CERTIFICATE_X509_KEY_CONSTRUCTOR = certificateX509KeyConstructor;
225         CERT_IMPL_GET_HANDLE = certImplGetHandle;
226         CERT_IMPL_SIGN_HANDLE = certImplSignHandle;
227         ALGORITHM_ID_GET_HANDLE = algorithmIdGetHandle;
228         CERTIFICATE_ALORITHM_ID_CONSTRUCTOR = certificateAlgorithmIdConstructor;
229         SUPPORTED = supported;
230     }
231 
232     static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
233                              String algorithm) throws Exception {
234         if (!SUPPORTED) {
235             throw new UnsupportedOperationException(
236                     OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported on the used JDK version");
237         }
238         try {
239             PrivateKey key = keypair.getPrivate();
240 
241             // Prepare the information required for generating an X.509 certificate.
242             Object info = X509_CERT_INFO_CONSTRUCTOR.newInstance();
243             Object owner = X500_NAME_CONSTRUCTOR.newInstance("CN=" + fqdn);
244 
245             CERT_INFO_SET_HANDLE.invoke(info, "version", CERTIFICATE_VERSION_CONSTRUCTOR.newInstance(2));
246             CERT_INFO_SET_HANDLE.invoke(info, "serialNumber",
247                     CERTIFICATE_SERIAL_NUMBER_CONSTRUCTOR.newInstance(new BigInteger(64, random)));
248             try {
249                 CERT_INFO_SET_HANDLE.invoke(info, "subject", CERTIFICATE_SUBJECT_NAME_CONSTRUCTOR.newInstance(owner));
250             } catch (InvocationTargetException ex) {
251                 CERT_INFO_SET_HANDLE.invoke(info, "subject", owner);
252             }
253             try {
254                 CERT_INFO_SET_HANDLE.invoke(info, "issuer", ISSUER_NAME_CONSTRUCTOR.newInstance(owner));
255             } catch (InvocationTargetException ex) {
256                 CERT_INFO_SET_HANDLE.invoke(info, "issuer", owner);
257             }
258             CERT_INFO_SET_HANDLE.invoke(info, "validity",
259                     CERTIFICATE_VALIDITY_CONSTRUCTOR.newInstance(notBefore, notAfter));
260             CERT_INFO_SET_HANDLE.invoke(info, "key", CERTIFICATE_X509_KEY_CONSTRUCTOR.newInstance(keypair.getPublic()));
261             CERT_INFO_SET_HANDLE.invoke(info, "algorithmID",
262                     // sha256WithRSAEncryption
263                     CERTIFICATE_ALORITHM_ID_CONSTRUCTOR.newInstance(
264                             ALGORITHM_ID_GET_HANDLE.invoke(null, "1.2.840.113549.1.1.11")));
265 
266             // Sign the cert to identify the algorithm that's used.
267             Object cert = CERT_IMPL_CONSTRUCTOR.newInstance(info);
268             CERT_IMPL_SIGN_HANDLE.invoke(cert, key,
269                     algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
270 
271             // Update the algorithm and sign again.
272             CERT_INFO_SET_HANDLE.invoke(info, "algorithmID.algorithm",
273                     CERT_IMPL_GET_HANDLE.invoke(cert, "x509.algorithm"));
274             cert = CERT_IMPL_CONSTRUCTOR.newInstance(info);
275             CERT_IMPL_SIGN_HANDLE.invoke(cert, key,
276                     algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
277 
278             X509Certificate x509Cert = (X509Certificate) cert;
279             x509Cert.verify(keypair.getPublic());
280 
281             return newSelfSignedCertificate(fqdn, key, x509Cert);
282         } catch (Throwable cause) {
283             if (cause instanceof Exception) {
284                 throw (Exception) cause;
285             }
286             if (cause instanceof Error) {
287                 throw (Error) cause;
288             }
289             throw new IllegalStateException(cause);
290         }
291     }
292 
293     private OpenJdkSelfSignedCertGenerator() {
294     }
295 }