View Javadoc
1   /*
2    * Copyright 2016 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    *   http://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  package io.netty.handler.ssl;
17  
18  import io.netty.buffer.ByteBufAllocator;
19  import io.netty.internal.tcnative.CertificateRequestedCallback;
20  import io.netty.internal.tcnative.SSL;
21  
22  import javax.net.ssl.SSLException;
23  import javax.net.ssl.X509KeyManager;
24  import javax.security.auth.x500.X500Principal;
25  import java.security.PrivateKey;
26  import java.security.cert.X509Certificate;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.freeBio;
33  import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
34  
35  /**
36   * Manages key material for {@link OpenSslEngine}s and so set the right {@link PrivateKey}s and
37   * {@link X509Certificate}s.
38   */
39  class OpenSslKeyMaterialManager {
40  
41      // Code in this class is inspired by code of conscrypts:
42      // - https://android.googlesource.com/platform/external/
43      //   conscrypt/+/master/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
44      // - https://android.googlesource.com/platform/external/
45      //   conscrypt/+/master/src/main/java/org/conscrypt/SSLParametersImpl.java
46      //
47      static final String KEY_TYPE_RSA = "RSA";
48      static final String KEY_TYPE_DH_RSA = "DH_RSA";
49      static final String KEY_TYPE_EC = "EC";
50      static final String KEY_TYPE_EC_EC = "EC_EC";
51      static final String KEY_TYPE_EC_RSA = "EC_RSA";
52  
53      // key type mappings for types.
54      private static final Map<String, String> KEY_TYPES = new HashMap<String, String>();
55      static {
56          KEY_TYPES.put("RSA", KEY_TYPE_RSA);
57          KEY_TYPES.put("DHE_RSA", KEY_TYPE_RSA);
58          KEY_TYPES.put("ECDHE_RSA", KEY_TYPE_RSA);
59          KEY_TYPES.put("ECDHE_ECDSA", KEY_TYPE_EC);
60          KEY_TYPES.put("ECDH_RSA", KEY_TYPE_EC_RSA);
61          KEY_TYPES.put("ECDH_ECDSA", KEY_TYPE_EC_EC);
62          KEY_TYPES.put("DH_RSA", KEY_TYPE_DH_RSA);
63      }
64  
65      private final X509KeyManager keyManager;
66      private final String password;
67  
68      OpenSslKeyMaterialManager(X509KeyManager keyManager, String password) {
69          this.keyManager = keyManager;
70          this.password = password;
71      }
72  
73      void setKeyMaterial(ReferenceCountedOpenSslEngine engine) throws SSLException {
74          long ssl = engine.sslPointer();
75          String[] authMethods = SSL.authenticationMethods(ssl);
76          Set<String> aliases = new HashSet<String>(authMethods.length);
77          for (String authMethod : authMethods) {
78              String type = KEY_TYPES.get(authMethod);
79              if (type != null) {
80                  String alias = chooseServerAlias(engine, type);
81                  if (alias != null && aliases.add(alias)) {
82                      setKeyMaterial(ssl, alias);
83                  }
84              }
85          }
86      }
87  
88      CertificateRequestedCallback.KeyMaterial keyMaterial(ReferenceCountedOpenSslEngine engine, String[] keyTypes,
89                                                           X500Principal[] issuer) throws SSLException {
90          String alias = chooseClientAlias(engine, keyTypes, issuer);
91          long keyBio = 0;
92          long keyCertChainBio = 0;
93          long pkey = 0;
94          long certChain = 0;
95  
96          try {
97              // TODO: Should we cache these and so not need to do a memory copy all the time ?
98              X509Certificate[] certificates = keyManager.getCertificateChain(alias);
99              if (certificates == null || certificates.length == 0) {
100                 return null;
101             }
102 
103             PrivateKey key = keyManager.getPrivateKey(alias);
104             keyCertChainBio = toBIO(certificates);
105             certChain = SSL.parseX509Chain(keyCertChainBio);
106             if (key != null) {
107                 keyBio = toBIO(key);
108                 pkey = SSL.parsePrivateKey(keyBio, password);
109             }
110             CertificateRequestedCallback.KeyMaterial material = new CertificateRequestedCallback.KeyMaterial(
111                     certChain, pkey);
112 
113             // Reset to 0 so we do not free these. This is needed as the client certificate callback takes ownership
114             // of both the key and the certificate if they are returned from this method, and thus must not
115             // be freed here.
116             certChain = pkey = 0;
117             return material;
118         } catch (SSLException e) {
119             throw e;
120         } catch (Exception e) {
121             throw new SSLException(e);
122         } finally {
123             freeBio(keyBio);
124             freeBio(keyCertChainBio);
125             SSL.freePrivateKey(pkey);
126             SSL.freeX509Chain(certChain);
127         }
128     }
129 
130     private void setKeyMaterial(long ssl, String alias) throws SSLException {
131         long keyBio = 0;
132         long keyCertChainBio = 0;
133         long keyCertChainBio2 = 0;
134 
135         try {
136             // TODO: Should we cache these and so not need to do a memory copy all the time ?
137             X509Certificate[] certificates = keyManager.getCertificateChain(alias);
138             if (certificates == null || certificates.length == 0) {
139                 return;
140             }
141 
142             PrivateKey key = keyManager.getPrivateKey(alias);
143 
144             // Only encode one time
145             PemEncoded encoded = PemX509Certificate.toPEM(ByteBufAllocator.DEFAULT, true, certificates);
146             try {
147                 keyCertChainBio = toBIO(ByteBufAllocator.DEFAULT, encoded.retain());
148                 keyCertChainBio2 = toBIO(ByteBufAllocator.DEFAULT, encoded.retain());
149 
150                 if (key != null) {
151                     keyBio = toBIO(key);
152                 }
153                 SSL.setCertificateBio(ssl, keyCertChainBio, keyBio, password);
154 
155                 // We may have more then one cert in the chain so add all of them now.
156                 SSL.setCertificateChainBio(ssl, keyCertChainBio2, true);
157             } finally {
158                 encoded.release();
159             }
160         } catch (SSLException e) {
161             throw e;
162         } catch (Exception e) {
163             throw new SSLException(e);
164         } finally {
165             freeBio(keyBio);
166             freeBio(keyCertChainBio);
167             freeBio(keyCertChainBio2);
168         }
169     }
170 
171     protected String chooseClientAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine,
172                                        String[] keyTypes, X500Principal[] issuer) {
173         return keyManager.chooseClientAlias(keyTypes, issuer, null);
174     }
175 
176     protected String chooseServerAlias(@SuppressWarnings("unused") ReferenceCountedOpenSslEngine engine, String type) {
177         return keyManager.chooseServerAlias(type, null, null);
178     }
179 }