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          try (InputStream chainInputStream = Files.newInputStream(chain.toPath())) {
75              return newKeyless(privateKeyMethod, chainInputStream);
76          }
77      }
78  
79      /**
80       * Creates a new factory instance.
81       *
82       * @param privateKeyMethod              the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
83       * @param chain                         the {@link InputStream} that contains the {@link X509Certificate} chain.
84       * @return                              a new factory instance.
85       * @throws CertificateException         on error.
86       * @throws IOException                  on error.
87       * @throws KeyStoreException            on error.
88       * @throws NoSuchAlgorithmException     on error.
89       * @throws UnrecoverableKeyException    on error.
90       */
91      public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
92                                                              InputStream chain)
93              throws CertificateException, IOException,
94              KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
95          return newKeyless(privateKeyMethod, QuicSslContext.toX509Certificates0(chain));
96      }
97  
98      /**
99       * Creates a new factory instance.
100      *
101      * @param privateKeyMethod              the {@link BoringSSLAsyncPrivateKeyMethod} that is used for key signing.
102      * @param certificateChain              the {@link X509Certificate} chain.
103      * @return                              a new factory instance.
104      * @throws CertificateException         on error.
105      * @throws IOException                  on error.
106      * @throws KeyStoreException            on error.
107      * @throws NoSuchAlgorithmException     on error.
108      * @throws UnrecoverableKeyException    on error.
109      */
110     public static BoringSSLKeylessManagerFactory newKeyless(BoringSSLAsyncPrivateKeyMethod privateKeyMethod,
111                                                             X509Certificate... certificateChain)
112             throws CertificateException, IOException,
113             KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
114         checkNotNull(certificateChain, "certificateChain");
115         KeyStore store = new KeylessKeyStore(certificateChain.clone());
116         store.load(null, null);
117         BoringSSLKeylessManagerFactory factory = new BoringSSLKeylessManagerFactory(
118                 KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()), privateKeyMethod);
119         factory.init(store, null);
120         return factory;
121     }
122 
123     private static final class KeylessManagerFactorySpi extends KeyManagerFactorySpi {
124 
125         private final KeyManagerFactory keyManagerFactory;
126 
127         KeylessManagerFactorySpi(KeyManagerFactory keyManagerFactory) {
128             this.keyManagerFactory = requireNonNull(keyManagerFactory, "keyManagerFactory");
129         }
130 
131         @Override
132         protected void engineInit(KeyStore ks, char[] password)
133                 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
134             keyManagerFactory.init(ks, password);
135         }
136 
137         @Override
138         protected void engineInit(ManagerFactoryParameters spec) {
139             throw new UnsupportedOperationException("Not supported");
140         }
141 
142         @Override
143         protected KeyManager[] engineGetKeyManagers() {
144             return keyManagerFactory.getKeyManagers();
145         }
146     }
147     private static final class KeylessKeyStore extends KeyStore {
148         private static final String ALIAS = "key";
149         private KeylessKeyStore(final X509Certificate[] certificateChain) {
150             super(new KeyStoreSpi() {
151 
152                 private final Date creationDate = new Date();
153 
154                 @Override
155                 @Nullable
156                 public Key engineGetKey(String alias, char[] password) {
157                     if (engineContainsAlias(alias)) {
158                         return BoringSSLKeylessPrivateKey.INSTANCE;
159                     }
160                     return null;
161                 }
162 
163                 @Override
164                 public Certificate @Nullable [] engineGetCertificateChain(String alias) {
165                     return engineContainsAlias(alias)? certificateChain.clone() : null;
166                 }
167 
168                 @Override
169                 @Nullable
170                 public Certificate engineGetCertificate(String alias) {
171                     return engineContainsAlias(alias)? certificateChain[0] : null;
172                 }
173 
174                 @Override
175                 @Nullable
176                 public Date engineGetCreationDate(String alias) {
177                     return engineContainsAlias(alias)? creationDate : null;
178                 }
179 
180                 @Override
181                 public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
182                         throws KeyStoreException {
183                     throw new KeyStoreException("Not supported");
184                 }
185 
186                 @Override
187                 public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
188                     throw new KeyStoreException("Not supported");
189                 }
190 
191                 @Override
192                 public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
193                     throw new KeyStoreException("Not supported");
194                 }
195 
196                 @Override
197                 public void engineDeleteEntry(String alias) throws KeyStoreException {
198                     throw new KeyStoreException("Not supported");
199                 }
200 
201                 @Override
202                 public Enumeration<String> engineAliases() {
203                     return Collections.enumeration(Collections.singleton(ALIAS));
204                 }
205 
206                 @Override
207                 public boolean engineContainsAlias(String alias) {
208                     return ALIAS.equals(alias);
209                 }
210 
211                 @Override
212                 public int engineSize() {
213                     return 1;
214                 }
215 
216                 @Override
217                 public boolean engineIsKeyEntry(String alias) {
218                     return engineContainsAlias(alias);
219                 }
220 
221                 @Override
222                 public boolean engineIsCertificateEntry(String alias) {
223                     return engineContainsAlias(alias);
224                 }
225 
226                 @Override
227                 @Nullable
228                 public String engineGetCertificateAlias(Certificate cert) {
229                     if (cert instanceof X509Certificate) {
230                         for (X509Certificate x509Certificate : certificateChain) {
231                             if (x509Certificate.equals(cert)) {
232                                 return ALIAS;
233                             }
234                         }
235                     }
236                     return null;
237                 }
238 
239                 @Override
240                 public void engineStore(OutputStream stream, char[] password) {
241                     throw new UnsupportedOperationException();
242                 }
243 
244                 @Override
245                 public void engineLoad(@Nullable InputStream stream, char @Nullable [] password) {
246                     if (stream != null && password != null) {
247                         throw new UnsupportedOperationException();
248                     }
249                 }
250             }, null, "keyless");
251         }
252     }
253 }