View Javadoc
1   /*
2    * Copyright 2022 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.codec.quic;
17  
18  import org.jetbrains.annotations.Nullable;
19  
20  import javax.net.ssl.KeyManager;
21  import javax.net.ssl.KeyManagerFactory;
22  import javax.net.ssl.KeyManagerFactorySpi;
23  import javax.net.ssl.ManagerFactoryParameters;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.nio.file.Files;
29  import java.security.Key;
30  import java.security.KeyStore;
31  import java.security.KeyStoreException;
32  import java.security.KeyStoreSpi;
33  import java.security.NoSuchAlgorithmException;
34  import java.security.UnrecoverableKeyException;
35  import java.security.cert.Certificate;
36  import java.security.cert.CertificateException;
37  import java.security.cert.X509Certificate;
38  import java.util.Collections;
39  import java.util.Date;
40  import java.util.Enumeration;
41  
42  import static io.netty.util.internal.ObjectUtil.checkNotNull;
43  import static java.util.Objects.requireNonNull;
44  
45  /**
46   * {@link KeyManagerFactory} that can be used to support custom key signing via {@link BoringSSLAsyncPrivateKeyMethod}.
47   */
48  public final class BoringSSLKeylessManagerFactory extends KeyManagerFactory {
49  
50      final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
51  
52      private BoringSSLKeylessManagerFactory(KeyManagerFactory keyManagerFactory,
53                                             BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
54          super(new KeylessManagerFactorySpi(keyManagerFactory),
55                  keyManagerFactory.getProvider(), keyManagerFactory.getAlgorithm());
56          this.privateKeyMethod = requireNonNull(privateKeyMethod, "privateKeyMethod");
57      }
58  
59      /**
60       * Creates a new factory instance.
61       *
62       * @param privateKeyMethod              the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
63       * @param chain                         the {@link File} that contains the {@link X509Certificate} chain.
64       * @return                              a new factory instance.
65       * @throws CertificateException         on error.
66       * @throws IOException                  on error.
67       * @throws KeyStoreException            on error.
68       * @throws NoSuchAlgorithmException     on error.
69       * @throws UnrecoverableKeyException    on error.
70       */
71      public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod, File chain)
72              throws CertificateException, IOException,
73              KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
74          return newKeyless(privateKeyMethod, Files.newInputStream(chain.toPath()));
75      }
76  
77      /**
78       * Creates a new factory instance.
79       *
80       * @param privateKeyMethod              the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
81       * @param chain                         the {@link InputStream} that contains the {@link X509Certificate} chain.
82       * @return                              a new factory instance.
83       * @throws CertificateException         on error.
84       * @throws IOException                  on error.
85       * @throws KeyStoreException            on error.
86       * @throws NoSuchAlgorithmException     on error.
87       * @throws UnrecoverableKeyException    on error.
88       */
89      public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
90                                                              InputStream chain)
91              throws CertificateException, IOException,
92              KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
93          return newKeyless(privateKeyMethod, QuicSslContext.toX509Certificates0(chain));
94      }
95  
96      /**
97       * Creates a new factory instance.
98       *
99       * @param privateKeyMethod              the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
100      * @param certificateChain              the {@link X509Certificate} chain.
101      * @return                              a new factory instance.
102      * @throws CertificateException         on error.
103      * @throws IOException                  on error.
104      * @throws KeyStoreException            on error.
105      * @throws NoSuchAlgorithmException     on error.
106      * @throws UnrecoverableKeyException    on error.
107      */
108     public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
109                                                             X509Certificate... certificateChain)
110             throws CertificateException, IOException,
111             KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
112         checkNotNull(certificateChain, "certificateChain");
113         KeyStore store = new KeylessKeyStore(certificateChain.clone());
114         store.load(null, null);
115         BoringSSLKeylessManagerFactory factory = new BoringSSLKeylessManagerFactory(
116                 KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()), privateKeyMethod);
117         factory.init(store, null);
118         return factory;
119     }
120 
121     private static final class KeylessManagerFactorySpi extends KeyManagerFactorySpi {
122 
123         private final KeyManagerFactory keyManagerFactory;
124 
125         KeylessManagerFactorySpi(KeyManagerFactory keyManagerFactory) {
126             this.keyManagerFactory = requireNonNull(keyManagerFactory, "keyManagerFactory");
127         }
128 
129         @Override
130         protected void engineInit(KeyStore ks, char[] password)
131                 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
132             keyManagerFactory.init(ks, password);
133         }
134 
135         @Override
136         protected void engineInit(ManagerFactoryParameters spec) {
137             throw new UnsupportedOperationException("Not supported");
138         }
139 
140         @Override
141         protected KeyManager[] engineGetKeyManagers() {
142             return keyManagerFactory.getKeyManagers();
143         }
144     }
145     private static final class KeylessKeyStore extends KeyStore {
146         private static final String ALIAS = "key";
147         private KeylessKeyStore(final X509Certificate[] certificateChain) {
148             super(new KeyStoreSpi() {
149 
150                 private final Date creationDate = new Date();
151 
152                 @Override
153                 @Nullable
154                 public Key engineGetKey(String alias, char[] password) {
155                     if (engineContainsAlias(alias)) {
156                         return BoringSSLKeylessPrivateKey.INSTANCE;
157                     }
158                     return null;
159                 }
160 
161                 @Override
162                 public Certificate @Nullable [] engineGetCertificateChain(String alias) {
163                     return engineContainsAlias(alias)? certificateChain.clone() : null;
164                 }
165 
166                 @Override
167                 @Nullable
168                 public Certificate engineGetCertificate(String alias) {
169                     return engineContainsAlias(alias)? certificateChain[0] : null;
170                 }
171 
172                 @Override
173                 @Nullable
174                 public Date engineGetCreationDate(String alias) {
175                     return engineContainsAlias(alias)? creationDate : null;
176                 }
177 
178                 @Override
179                 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
180                         throws KeyStoreException {
181                     throw new KeyStoreException("Not supported");
182                 }
183 
184                 @Override
185                 public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
186                     throw new KeyStoreException("Not supported");
187                 }
188 
189                 @Override
190                 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
191                     throw new KeyStoreException("Not supported");
192                 }
193 
194                 @Override
195                 public void engineDeleteEntry(String alias) throws KeyStoreException {
196                     throw new KeyStoreException("Not supported");
197                 }
198 
199                 @Override
200                 public Enumeration<String> engineAliases() {
201                     return Collections.enumeration(Collections.singleton(ALIAS));
202                 }
203 
204                 @Override
205                 public boolean engineContainsAlias(String alias) {
206                     return ALIAS.equals(alias);
207                 }
208 
209                 @Override
210                 public int engineSize() {
211                     return 1;
212                 }
213 
214                 @Override
215                 public boolean engineIsKeyEntry(String alias) {
216                     return engineContainsAlias(alias);
217                 }
218 
219                 @Override
220                 public boolean engineIsCertificateEntry(String alias) {
221                     return engineContainsAlias(alias);
222                 }
223 
224                 @Override
225                 @Nullable
226                 public String engineGetCertificateAlias(Certificate cert) {
227                     if (cert instanceof X509Certificate) {
228                         for (X509Certificate x509Certificate : certificateChain) {
229                             if (x509Certificate.equals(cert)) {
230                                 return ALIAS;
231                             }
232                         }
233                     }
234                     return null;
235                 }
236 
237                 @Override
238                 public void engineStore(OutputStream stream, char[] password) {
239                     throw new UnsupportedOperationException();
240                 }
241 
242                 @Override
243                 public void engineLoad(@Nullable InputStream stream, char @Nullable [] password) {
244                     if (stream != null && password != null) {
245                         throw new UnsupportedOperationException();
246                     }
247                 }
248             }, null, "keyless");
249         }
250     }
251 }