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