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.SuppressJava6Requirement;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  import sun.security.x509.AlgorithmId;
24  import sun.security.x509.CertificateAlgorithmId;
25  import sun.security.x509.CertificateSerialNumber;
26  import sun.security.x509.CertificateSubjectName;
27  import sun.security.x509.CertificateValidity;
28  import sun.security.x509.CertificateVersion;
29  import sun.security.x509.CertificateX509Key;
30  import sun.security.x509.X500Name;
31  import sun.security.x509.X509CertImpl;
32  import sun.security.x509.X509CertInfo;
33  
34  import java.lang.reflect.Constructor;
35  import java.lang.reflect.InvocationTargetException;
36  import java.lang.reflect.Method;
37  import java.security.AccessController;
38  import java.security.PrivilegedAction;
39  import java.util.Date;
40  import java.math.BigInteger;
41  import java.security.KeyPair;
42  import java.security.PrivateKey;
43  import java.security.SecureRandom;
44  import java.security.cert.CertificateException;
45  
46  import static io.netty.handler.ssl.util.SelfSignedCertificate.*;
47  
48  /**
49   * Generates a self-signed certificate using {@code sun.security.x509} package provided by OpenJDK.
50   */
51  final class OpenJdkSelfSignedCertGenerator {
52      private static final InternalLogger logger =
53              InternalLoggerFactory.getInstance(OpenJdkSelfSignedCertGenerator.class);
54      private static final Method CERT_INFO_SET_METHOD;
55      private static final Constructor<?> ISSUER_NAME_CONSTRUCTOR;
56      private static final Constructor<X509CertImpl> CERT_IMPL_CONSTRUCTOR;
57      private static final Method CERT_IMPL_GET_METHOD;
58      private static final Method CERT_IMPL_SIGN_METHOD;
59  
60      // Use reflection as JDK20+ did change things quite a bit.
61      static {
62          Method certInfoSetMethod = null;
63          Constructor<?> issuerNameConstructor = null;
64          Constructor<X509CertImpl> certImplConstructor = null;
65          Method certImplGetMethod = null;
66          Method certImplSignMethod = null;
67          try {
68              Object maybeCertInfoSetMethod = AccessController.doPrivileged(new PrivilegedAction<Object>() {
69                  @Override
70                  public Object run() {
71                      try {
72                          return X509CertInfo.class.getMethod("set", String.class, Object.class);
73                      } catch (Throwable cause) {
74                          return cause;
75                      }
76                  }
77              });
78              if (maybeCertInfoSetMethod instanceof Method) {
79                  certInfoSetMethod = (Method) maybeCertInfoSetMethod;
80              } else {
81                  throw (Throwable) maybeCertInfoSetMethod;
82              }
83  
84              Object maybeIssuerNameConstructor = AccessController.doPrivileged(new PrivilegedAction<Object>() {
85                  @Override
86                  public Object run() {
87                      try {
88                          Class<?> issuerName = Class.forName("sun.security.x509.CertificateIssuerName", false,
89                                  PlatformDependent.getClassLoader(OpenJdkSelfSignedCertGenerator.class));
90                          return issuerName.getConstructor(X500Name.class);
91                      } catch (Throwable cause) {
92                          return cause;
93                      }
94                  }
95              });
96              if (maybeIssuerNameConstructor instanceof Constructor) {
97                  issuerNameConstructor = (Constructor<?>) maybeIssuerNameConstructor;
98              } else {
99                  throw (Throwable) maybeIssuerNameConstructor;
100             }
101 
102             Object maybeCertImplConstructor = AccessController.doPrivileged(new PrivilegedAction<Object>() {
103                 @Override
104                 public Object run() {
105                     try {
106                         return X509CertImpl.class.getConstructor(X509CertInfo.class);
107                     } catch (Throwable cause) {
108                         return cause;
109                     }
110                 }
111             });
112             if (maybeCertImplConstructor instanceof Constructor) {
113                 @SuppressWarnings("unchecked")
114                 Constructor<X509CertImpl> constructor = (Constructor<X509CertImpl>) maybeCertImplConstructor;
115                 certImplConstructor = constructor;
116             } else {
117                 throw (Throwable) maybeCertImplConstructor;
118             }
119 
120             Object maybeCertImplGetMethod = AccessController.doPrivileged(new PrivilegedAction<Object>() {
121                 @Override
122                 public Object run() {
123                     try {
124                         return X509CertImpl.class.getMethod("get", String.class);
125                     } catch (Throwable cause) {
126                         return cause;
127                     }
128                 }
129             });
130             if (maybeCertImplGetMethod instanceof Method) {
131                 certImplGetMethod = (Method) maybeCertImplGetMethod;
132             } else {
133                 throw (Throwable) maybeCertImplGetMethod;
134             }
135 
136             Object maybeCertImplSignMethod = AccessController.doPrivileged(new PrivilegedAction<Object>() {
137                 @Override
138                 public Object run() {
139                     try {
140                         return X509CertImpl.class.getMethod("sign", PrivateKey.class, String.class);
141                     } catch (Throwable cause) {
142                         return cause;
143                     }
144                 }
145             });
146             if (maybeCertImplSignMethod instanceof Method) {
147                 certImplSignMethod = (Method) maybeCertImplSignMethod;
148             } else {
149                 throw (Throwable) maybeCertImplSignMethod;
150             }
151         } catch (Throwable cause) {
152             logger.debug(OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported", cause);
153         }
154         CERT_INFO_SET_METHOD = certInfoSetMethod;
155         ISSUER_NAME_CONSTRUCTOR = issuerNameConstructor;
156         CERT_IMPL_CONSTRUCTOR = certImplConstructor;
157         CERT_IMPL_GET_METHOD = certImplGetMethod;
158         CERT_IMPL_SIGN_METHOD = certImplSignMethod;
159     }
160 
161     @SuppressJava6Requirement(reason = "Usage guarded by dependency check")
162     static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
163                              String algorithm) throws Exception {
164         if (CERT_INFO_SET_METHOD == null || ISSUER_NAME_CONSTRUCTOR == null ||
165                 CERT_IMPL_CONSTRUCTOR == null || CERT_IMPL_GET_METHOD == null || CERT_IMPL_SIGN_METHOD == null) {
166             throw new UnsupportedOperationException(
167                     OpenJdkSelfSignedCertGenerator.class.getSimpleName() + " not supported on the used JDK version");
168         }
169         PrivateKey key = keypair.getPrivate();
170 
171         // Prepare the information required for generating an X.509 certificate.
172         X509CertInfo info = new X509CertInfo();
173         X500Name owner = new X500Name("CN=" + fqdn);
174 
175         CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
176         CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.SERIAL_NUMBER,
177                 new CertificateSerialNumber(new BigInteger(64, random)));
178         try {
179             CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
180         } catch (InvocationTargetException ex) {
181             if (ex.getCause() instanceof CertificateException) {
182                 CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.SUBJECT, owner);
183             } else {
184                 throw ex;
185             }
186         }
187         try {
188             CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.ISSUER, ISSUER_NAME_CONSTRUCTOR.newInstance(owner));
189         } catch (InvocationTargetException ex) {
190             if (ex.getCause() instanceof CertificateException) {
191                 CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.ISSUER, owner);
192             } else {
193                 throw ex;
194             }
195         }
196         CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.VALIDITY, new CertificateValidity(notBefore, notAfter));
197         CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.KEY, new CertificateX509Key(keypair.getPublic()));
198         CERT_INFO_SET_METHOD.invoke(info, X509CertInfo.ALGORITHM_ID,
199                 // sha256WithRSAEncryption
200                 new CertificateAlgorithmId(AlgorithmId.get("1.2.840.113549.1.1.11")));
201 
202         // Sign the cert to identify the algorithm that's used.
203         X509CertImpl cert = CERT_IMPL_CONSTRUCTOR.newInstance(info);
204         CERT_IMPL_SIGN_METHOD.invoke(cert, key, algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
205 
206         // Update the algorithm and sign again.
207         CERT_INFO_SET_METHOD.invoke(info, CertificateAlgorithmId.NAME + ".algorithm",
208                 CERT_IMPL_GET_METHOD.invoke(cert, "x509.algorithm"));
209         cert = CERT_IMPL_CONSTRUCTOR.newInstance(info);
210         CERT_IMPL_SIGN_METHOD.invoke(cert, key,
211                 algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
212         cert.verify(keypair.getPublic());
213 
214         return newSelfSignedCertificate(fqdn, key, cert);
215     }
216 
217     private OpenJdkSelfSignedCertGenerator() { }
218 }