1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.ssl;
17
18 import io.netty.internal.tcnative.SSL;
19 import io.netty5.buffer.api.BufferAllocator;
20 import io.netty5.buffer.api.DefaultBufferAllocators;
21 import io.netty5.util.Resource;
22
23 import javax.net.ssl.KeyManager;
24 import javax.net.ssl.KeyManagerFactory;
25 import javax.net.ssl.KeyManagerFactorySpi;
26 import javax.net.ssl.ManagerFactoryParameters;
27 import javax.net.ssl.X509KeyManager;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.security.InvalidAlgorithmParameterException;
33 import java.security.Key;
34 import java.security.KeyStore;
35 import java.security.KeyStoreException;
36 import java.security.KeyStoreSpi;
37 import java.security.NoSuchAlgorithmException;
38 import java.security.PrivateKey;
39 import java.security.Provider;
40 import java.security.UnrecoverableKeyException;
41 import java.security.cert.Certificate;
42 import java.security.cert.CertificateException;
43 import java.security.cert.X509Certificate;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.Enumeration;
47 import java.util.HashMap;
48 import java.util.Map;
49
50 import static io.netty5.util.internal.ObjectUtil.checkNonEmpty;
51 import static java.util.Objects.requireNonNull;
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public final class OpenSslX509KeyManagerFactory extends KeyManagerFactory {
66
67 private final OpenSslKeyManagerFactorySpi spi;
68
69 public OpenSslX509KeyManagerFactory() {
70 this(newOpenSslKeyManagerFactorySpi(null));
71 }
72
73 public OpenSslX509KeyManagerFactory(Provider provider) {
74 this(newOpenSslKeyManagerFactorySpi(provider));
75 }
76
77 public OpenSslX509KeyManagerFactory(String algorithm, Provider provider) throws NoSuchAlgorithmException {
78 this(newOpenSslKeyManagerFactorySpi(algorithm, provider));
79 }
80
81 private OpenSslX509KeyManagerFactory(OpenSslKeyManagerFactorySpi spi) {
82 super(spi, spi.kmf.getProvider(), spi.kmf.getAlgorithm());
83 this.spi = spi;
84 }
85
86 private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(Provider provider) {
87 try {
88 return newOpenSslKeyManagerFactorySpi(null, provider);
89 } catch (NoSuchAlgorithmException e) {
90
91 throw new IllegalStateException(e);
92 }
93 }
94
95 private static OpenSslKeyManagerFactorySpi newOpenSslKeyManagerFactorySpi(String algorithm, Provider provider)
96 throws NoSuchAlgorithmException {
97 if (algorithm == null) {
98 algorithm = KeyManagerFactory.getDefaultAlgorithm();
99 }
100 return new OpenSslKeyManagerFactorySpi(
101 provider == null ? KeyManagerFactory.getInstance(algorithm) :
102 KeyManagerFactory.getInstance(algorithm, provider));
103 }
104
105 OpenSslKeyMaterialProvider newProvider() {
106 return spi.newProvider();
107 }
108
109 private static final class OpenSslKeyManagerFactorySpi extends KeyManagerFactorySpi {
110 final KeyManagerFactory kmf;
111 private volatile ProviderFactory providerFactory;
112
113 OpenSslKeyManagerFactorySpi(KeyManagerFactory kmf) {
114 this.kmf = requireNonNull(kmf, "kmf");
115 }
116
117 @Override
118 protected synchronized void engineInit(KeyStore keyStore, char[] chars)
119 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
120 if (providerFactory != null) {
121 throw new KeyStoreException("Already initialized");
122 }
123 if (!keyStore.aliases().hasMoreElements()) {
124 throw new KeyStoreException("No aliases found");
125 }
126
127 kmf.init(keyStore, chars);
128 providerFactory = new ProviderFactory(ReferenceCountedOpenSslContext.chooseX509KeyManager(
129 kmf.getKeyManagers()), password(chars), Collections.list(keyStore.aliases()));
130 }
131
132 private static String password(char[] password) {
133 if (password == null || password.length == 0) {
134 return null;
135 }
136 return new String(password);
137 }
138
139 @Override
140 protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
141 throws InvalidAlgorithmParameterException {
142 throw new InvalidAlgorithmParameterException("Not supported");
143 }
144
145 @Override
146 protected KeyManager[] engineGetKeyManagers() {
147 ProviderFactory providerFactory = this.providerFactory;
148 if (providerFactory == null) {
149 throw new IllegalStateException("engineInit(...) not called yet");
150 }
151 return new KeyManager[] { providerFactory.keyManager };
152 }
153
154 OpenSslKeyMaterialProvider newProvider() {
155 ProviderFactory providerFactory = this.providerFactory;
156 if (providerFactory == null) {
157 throw new IllegalStateException("engineInit(...) not called yet");
158 }
159 return providerFactory.newProvider();
160 }
161
162 private static final class ProviderFactory {
163 private final X509KeyManager keyManager;
164 private final String password;
165 private final Iterable<String> aliases;
166
167 ProviderFactory(X509KeyManager keyManager, String password, Iterable<String> aliases) {
168 this.keyManager = keyManager;
169 this.password = password;
170 this.aliases = aliases;
171 }
172
173 OpenSslKeyMaterialProvider newProvider() {
174 return new OpenSslPopulatedKeyMaterialProvider(keyManager,
175 password, aliases);
176 }
177
178
179
180
181
182 private static final class OpenSslPopulatedKeyMaterialProvider extends OpenSslKeyMaterialProvider {
183 private final Map<String, Object> materialMap;
184
185 OpenSslPopulatedKeyMaterialProvider(
186 X509KeyManager keyManager, String password, Iterable<String> aliases) {
187 super(keyManager, password);
188 materialMap = new HashMap<>();
189 boolean initComplete = false;
190 try {
191 for (String alias: aliases) {
192 if (alias != null && !materialMap.containsKey(alias)) {
193 try {
194 materialMap.put(alias, super.chooseKeyMaterial(
195 DefaultBufferAllocators.offHeapAllocator(), alias));
196 } catch (Exception e) {
197
198
199 materialMap.put(alias, e);
200 }
201 }
202 }
203 initComplete = true;
204 } finally {
205 if (!initComplete) {
206 destroy();
207 }
208 }
209 checkNonEmpty(materialMap, "materialMap");
210 }
211
212 @Override
213 OpenSslKeyMaterial chooseKeyMaterial(BufferAllocator allocator, String alias) throws Exception {
214 Object value = materialMap.get(alias);
215 if (value == null) {
216
217 return null;
218 }
219 if (value instanceof OpenSslKeyMaterial) {
220 return ((OpenSslKeyMaterial) value).retain();
221 }
222 throw (Exception) value;
223 }
224
225 @Override
226 void destroy() {
227 for (Object material: materialMap.values()) {
228 Resource.dispose(material);
229 }
230 materialMap.clear();
231 }
232 }
233 }
234 }
235
236
237
238
239
240
241
242 public static OpenSslX509KeyManagerFactory newEngineBased(File certificateChain, String password)
243 throws CertificateException, IOException,
244 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
245 return newEngineBased(SslContext.toX509Certificates(certificateChain), password);
246 }
247
248
249
250
251
252
253
254 public static OpenSslX509KeyManagerFactory newEngineBased(X509Certificate[] certificateChain, String password)
255 throws CertificateException, IOException,
256 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
257 requireNonNull(certificateChain, "certificateChain");
258 KeyStore store = new OpenSslKeyStore(certificateChain.clone(), false);
259 store.load(null, null);
260 OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
261 factory.init(store, password == null ? null : password.toCharArray());
262 return factory;
263 }
264
265
266
267
268 public static OpenSslX509KeyManagerFactory newKeyless(File chain)
269 throws CertificateException, IOException,
270 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
271 return newKeyless(SslContext.toX509Certificates(chain));
272 }
273
274
275
276
277 public static OpenSslX509KeyManagerFactory newKeyless(InputStream chain)
278 throws CertificateException, IOException,
279 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
280 return newKeyless(SslContext.toX509Certificates(chain));
281 }
282
283
284
285
286
287 public static OpenSslX509KeyManagerFactory newKeyless(X509Certificate... certificateChain)
288 throws CertificateException, IOException,
289 KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
290 requireNonNull(certificateChain, "certificateChain");
291 KeyStore store = new OpenSslKeyStore(certificateChain.clone(), true);
292 store.load(null, null);
293 OpenSslX509KeyManagerFactory factory = new OpenSslX509KeyManagerFactory();
294 factory.init(store, null);
295 return factory;
296 }
297
298 private static final class OpenSslKeyStore extends KeyStore {
299 private OpenSslKeyStore(final X509Certificate[] certificateChain, final boolean keyless) {
300 super(new KeyStoreSpi() {
301
302 private final Date creationDate = new Date();
303
304 @Override
305 public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
306 if (engineContainsAlias(alias)) {
307 final long privateKeyAddress;
308 if (keyless) {
309 privateKeyAddress = 0;
310 } else {
311 try {
312 privateKeyAddress = SSL.loadPrivateKeyFromEngine(
313 alias, password == null ? null : new String(password));
314 } catch (Exception e) {
315 UnrecoverableKeyException keyException =
316 new UnrecoverableKeyException("Unable to load key from engine");
317 keyException.initCause(e);
318 throw keyException;
319 }
320 }
321 return new OpenSslPrivateKey(privateKeyAddress);
322 }
323 return null;
324 }
325
326 @Override
327 public Certificate[] engineGetCertificateChain(String alias) {
328 return engineContainsAlias(alias)? certificateChain.clone() : null;
329 }
330
331 @Override
332 public Certificate engineGetCertificate(String alias) {
333 return engineContainsAlias(alias)? certificateChain[0] : null;
334 }
335
336 @Override
337 public Date engineGetCreationDate(String alias) {
338 return engineContainsAlias(alias)? creationDate : null;
339 }
340
341 @Override
342 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
343 throws KeyStoreException {
344 throw new KeyStoreException("Not supported");
345 }
346
347 @Override
348 public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
349 throw new KeyStoreException("Not supported");
350 }
351
352 @Override
353 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
354 throw new KeyStoreException("Not supported");
355 }
356
357 @Override
358 public void engineDeleteEntry(String alias) throws KeyStoreException {
359 throw new KeyStoreException("Not supported");
360 }
361
362 @Override
363 public Enumeration<String> engineAliases() {
364 return Collections.enumeration(Collections.singleton(SslContext.ALIAS));
365 }
366
367 @Override
368 public boolean engineContainsAlias(String alias) {
369 return SslContext.ALIAS.equals(alias);
370 }
371
372 @Override
373 public int engineSize() {
374 return 1;
375 }
376
377 @Override
378 public boolean engineIsKeyEntry(String alias) {
379 return engineContainsAlias(alias);
380 }
381
382 @Override
383 public boolean engineIsCertificateEntry(String alias) {
384 return engineContainsAlias(alias);
385 }
386
387 @Override
388 public String engineGetCertificateAlias(Certificate cert) {
389 if (cert instanceof X509Certificate) {
390 for (X509Certificate x509Certificate : certificateChain) {
391 if (x509Certificate.equals(cert)) {
392 return SslContext.ALIAS;
393 }
394 }
395 }
396 return null;
397 }
398
399 @Override
400 public void engineStore(OutputStream stream, char[] password) {
401 throw new UnsupportedOperationException();
402 }
403
404 @Override
405 public void engineLoad(InputStream stream, char[] password) {
406 if (stream != null && password != null) {
407 throw new UnsupportedOperationException();
408 }
409 }
410 }, null, "native");
411
412 OpenSsl.ensureAvailability();
413 }
414 }
415 }