View Javadoc
1   /*
2    * Copyright 2018 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  package io.netty.handler.ssl;
17  
18  import io.netty.buffer.ByteBufAllocator;
19  import io.netty.buffer.UnpooledByteBufAllocator;
20  import io.netty.internal.tcnative.SSL;
21  import io.netty.util.IllegalReferenceCountException;
22  
23  import javax.net.ssl.SSLException;
24  import javax.net.ssl.X509KeyManager;
25  import java.security.PrivateKey;
26  import java.security.cert.X509Certificate;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import static io.netty.handler.ssl.ReferenceCountedOpenSslContext.toBIO;
30  
31  /**
32   * Provides {@link OpenSslKeyMaterial} for a given alias.
33   */
34  class OpenSslKeyMaterialProvider {
35      private static final MaterialCache SENTINEL_DESTROYED = new MaterialCache(null, null, null);
36  
37      private final X509KeyManager keyManager;
38      private final String password;
39      private final AtomicReference<MaterialCache> cache;
40  
41      OpenSslKeyMaterialProvider(X509KeyManager keyManager, String password) {
42          this.keyManager = keyManager;
43          this.password = password;
44          cache = new AtomicReference<MaterialCache>();
45      }
46  
47      static void validateKeyMaterialSupported(X509Certificate[] keyCertChain, PrivateKey key, String keyPassword)
48              throws SSLException {
49          validateSupported(keyCertChain);
50          validateSupported(key, keyPassword);
51      }
52  
53      private static void validateSupported(PrivateKey key, String password) throws SSLException {
54          if (key == null) {
55              return;
56          }
57  
58          long pkeyBio = 0;
59          long pkey = 0;
60  
61          try {
62              pkeyBio = toBIO(UnpooledByteBufAllocator.DEFAULT, key);
63              pkey = SSL.parsePrivateKey(pkeyBio, password);
64          } catch (Exception e) {
65              throw new SSLException("PrivateKey type not supported " + key.getFormat(), e);
66          } finally {
67              SSL.freeBIO(pkeyBio);
68              if (pkey != 0) {
69                  SSL.freePrivateKey(pkey);
70              }
71          }
72      }
73  
74      private static void validateSupported(X509Certificate[] certificates) throws SSLException {
75          if (certificates == null || certificates.length == 0) {
76              return;
77          }
78  
79          long chainBio = 0;
80          long chain = 0;
81          PemEncoded encoded = null;
82          try {
83              encoded = PemX509Certificate.toPEM(UnpooledByteBufAllocator.DEFAULT, true, certificates);
84              chainBio = toBIO(UnpooledByteBufAllocator.DEFAULT, encoded.retain());
85              chain = SSL.parseX509Chain(chainBio);
86          } catch (Exception e) {
87              throw new SSLException("Certificate type not supported", e);
88          } finally {
89              SSL.freeBIO(chainBio);
90              if (chain != 0) {
91                  SSL.freeX509Chain(chain);
92              }
93              if (encoded != null) {
94                  encoded.release();
95              }
96          }
97      }
98  
99      /**
100      * Returns the underlying {@link X509KeyManager} that is used.
101      */
102     X509KeyManager keyManager() {
103         return keyManager;
104     }
105 
106     /**
107      * Returns the {@link OpenSslKeyMaterial} or {@code null} (if none) that should be used during the handshake by
108      * OpenSSL.
109      */
110     OpenSslKeyMaterial chooseKeyMaterial(ByteBufAllocator allocator, String alias) throws Exception {
111         X509Certificate[] certificates = keyManager.getCertificateChain(alias);
112         if (certificates == null || certificates.length == 0) {
113             return null;
114         }
115 
116         PrivateKey key = keyManager.getPrivateKey(alias);
117         MaterialCache materialCache = cache.get();
118         if (materialCache != null && materialCache != SENTINEL_DESTROYED && materialCache.retain()) {
119             if (materialCache.sameInstances(key, certificates)) {
120                 return materialCache.material(); // We already called `retain()`
121             } else {
122                 // No match on this cache. Release and build a new one from scratch.
123                 materialCache.release();
124             }
125         }
126 
127         OpenSslKeyMaterial keyMaterial = createKeyMaterial(allocator, certificates, key);
128         materialCache = new MaterialCache(key, certificates, keyMaterial);
129 
130         // Retain the new material to put in the cache, then replace and release the old material.
131         materialCache.retain();
132         MaterialCache oldMaterial = cache.getAndSet(materialCache);
133         if (oldMaterial != null) {
134             if (oldMaterial == SENTINEL_DESTROYED) {
135                 destroyCache(); // Call `destroyCache()` instead of `destroy()` to avoid duplicating other effects.
136             } else {
137                 oldMaterial.release();
138             }
139         }
140 
141         return keyMaterial;
142     }
143 
144     private OpenSslKeyMaterial createKeyMaterial(
145             ByteBufAllocator allocator, X509Certificate[] certificates, PrivateKey key)
146             throws Exception {
147         PemEncoded encoded = PemX509Certificate.toPEM(allocator, true, certificates);
148         long chainBio = 0;
149         long pkeyBio = 0;
150         long chain = 0;
151         long pkey = 0;
152         try {
153             chainBio = toBIO(allocator, encoded.retain());
154             chain = SSL.parseX509Chain(chainBio);
155 
156             OpenSslKeyMaterial keyMaterial;
157             if (key instanceof OpenSslPrivateKey) {
158                 keyMaterial = ((OpenSslPrivateKey) key).newKeyMaterial(chain, certificates);
159             } else {
160                 pkeyBio = toBIO(allocator, key);
161                 pkey = key == null ? 0 : SSL.parsePrivateKey(pkeyBio, password);
162                 keyMaterial = new DefaultOpenSslKeyMaterial(chain, pkey, certificates);
163             }
164 
165             // See the chain and pkey to 0 so we will not release it as the ownership was
166             // transferred to OpenSslKeyMaterial.
167             chain = 0;
168             pkey = 0;
169             return keyMaterial;
170         } finally {
171             SSL.freeBIO(chainBio);
172             SSL.freeBIO(pkeyBio);
173             if (chain != 0) {
174                 SSL.freeX509Chain(chain);
175             }
176             if (pkey != 0) {
177                 SSL.freePrivateKey(pkey);
178             }
179             encoded.release();
180         }
181     }
182 
183     /**
184      * Will be invoked once the provider should be destroyed.
185      */
186     void destroy() {
187         destroyCache();
188     }
189 
190     private void destroyCache() {
191         MaterialCache oldMaterial;
192         while ((oldMaterial = cache.getAndSet(SENTINEL_DESTROYED)) != SENTINEL_DESTROYED) {
193             if (oldMaterial != null) {
194                 oldMaterial.release();
195             }
196         }
197     }
198 
199     private static final class MaterialCache {
200         private final PrivateKey key;
201         private final X509Certificate[] certs;
202         private final OpenSslKeyMaterial material;
203 
204         private MaterialCache(PrivateKey key, X509Certificate[] certs, OpenSslKeyMaterial material) {
205             this.key = key;
206             this.certs = certs;
207             this.material = material;
208         }
209 
210         OpenSslKeyMaterial material() {
211             return material;
212         }
213 
214         boolean sameInstances(PrivateKey key, X509Certificate[] certs) {
215             X509Certificate[] existingCerts = this.certs;
216             int length = existingCerts.length;
217             if (this.key != key || length != certs.length) {
218                 return false;
219             }
220             for (int i = 0; i < length; i++) {
221                 if (certs[i] != existingCerts[i]) {
222                     return false;
223                 }
224             }
225             return true;
226         }
227 
228         boolean retain() {
229             if (material.refCnt() != 0) {
230                 try {
231                     material.retain();
232                     return true;
233                 } catch (IllegalReferenceCountException ignore) {
234                     // Fall through to the `return false` below.
235                 }
236             }
237             return false;
238         }
239 
240         void release() {
241             material.release();
242         }
243     }
244 }