1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
101
102 X509KeyManager keyManager() {
103 return keyManager;
104 }
105
106
107
108
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();
121 } else {
122
123 materialCache.release();
124 }
125 }
126
127 OpenSslKeyMaterial keyMaterial = createKeyMaterial(allocator, certificates, key);
128 materialCache = new MaterialCache(key, certificates, keyMaterial);
129
130
131 materialCache.retain();
132 MaterialCache oldMaterial = cache.getAndSet(materialCache);
133 if (oldMaterial != null) {
134 if (oldMaterial == SENTINEL_DESTROYED) {
135 destroyCache();
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
166
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
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
235 }
236 }
237 return false;
238 }
239
240 void release() {
241 material.release();
242 }
243 }
244 }