• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt;
18 
19 import java.io.InputStream;
20 import java.security.InvalidKeyException;
21 import java.security.NoSuchAlgorithmException;
22 import java.security.PrivateKey;
23 import java.security.PublicKey;
24 import java.security.interfaces.ECPrivateKey;
25 import java.security.interfaces.RSAPrivateKey;
26 import java.security.spec.ECParameterSpec;
27 import java.security.spec.InvalidKeySpecException;
28 import java.security.spec.PKCS8EncodedKeySpec;
29 import java.security.spec.X509EncodedKeySpec;
30 import javax.crypto.SecretKey;
31 
32 public class OpenSSLKey {
33     private final NativeRef.EVP_PKEY ctx;
34 
35     private final OpenSSLEngine engine;
36 
37     private final String alias;
38 
39     private final boolean wrapped;
40 
OpenSSLKey(long ctx)41     public OpenSSLKey(long ctx) {
42         this(ctx, false);
43     }
44 
OpenSSLKey(long ctx, boolean wrapped)45     public OpenSSLKey(long ctx, boolean wrapped) {
46         this.ctx = new NativeRef.EVP_PKEY(ctx);
47         engine = null;
48         alias = null;
49         this.wrapped = wrapped;
50     }
51 
OpenSSLKey(long ctx, OpenSSLEngine engine, String alias)52     public OpenSSLKey(long ctx, OpenSSLEngine engine, String alias) {
53         this.ctx = new NativeRef.EVP_PKEY(ctx);
54         this.engine = engine;
55         this.alias = alias;
56         this.wrapped = false;
57     }
58 
59     /**
60      * Returns the EVP_PKEY context for use in JNI calls.
61      */
getNativeRef()62     public NativeRef.EVP_PKEY getNativeRef() {
63         return ctx;
64     }
65 
getEngine()66     OpenSSLEngine getEngine() {
67         return engine;
68     }
69 
isEngineBased()70     boolean isEngineBased() {
71         return engine != null;
72     }
73 
getAlias()74     public String getAlias() {
75         return alias;
76     }
77 
isWrapped()78     public boolean isWrapped() {
79         return wrapped;
80     }
81 
fromPrivateKey(PrivateKey key)82     public static OpenSSLKey fromPrivateKey(PrivateKey key) throws InvalidKeyException {
83         if (key instanceof OpenSSLKeyHolder) {
84             return ((OpenSSLKeyHolder) key).getOpenSSLKey();
85         }
86 
87         final String keyFormat = key.getFormat();
88         if (keyFormat == null) {
89             return wrapPrivateKey(key);
90         } else if (!"PKCS#8".equals(key.getFormat())) {
91             throw new InvalidKeyException("Unknown key format " + keyFormat);
92         }
93 
94         final byte[] encoded = key.getEncoded();
95         if (encoded == null) {
96             throw new InvalidKeyException("Key encoding is null");
97         }
98 
99         return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(key.getEncoded()));
100     }
101 
102     /**
103      * Parse a private key in PEM encoding from the provided input stream.
104      *
105      * @throws InvalidKeyException if parsing fails
106      */
fromPrivateKeyPemInputStream(InputStream is)107     public static OpenSSLKey fromPrivateKeyPemInputStream(InputStream is)
108             throws InvalidKeyException {
109         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
110         try {
111             long keyCtx = NativeCrypto.PEM_read_bio_PrivateKey(bis.getBioContext());
112             if (keyCtx == 0L) {
113                 return null;
114             }
115 
116             return new OpenSSLKey(keyCtx);
117         } catch (Exception e) {
118             throw new InvalidKeyException(e);
119         } finally {
120             bis.release();
121         }
122     }
123 
124     /**
125      * Gets an {@code OpenSSLKey} instance backed by the provided private key. The resulting key is
126      * usable only by this provider's TLS/SSL stack.
127      *
128      * @param privateKey private key.
129      * @param publicKey corresponding public key or {@code null} if not available. Some opaque
130      *        private keys cannot be used by the TLS/SSL stack without the public key.
131      */
fromPrivateKeyForTLSStackOnly( PrivateKey privateKey, PublicKey publicKey)132     public static OpenSSLKey fromPrivateKeyForTLSStackOnly(
133             PrivateKey privateKey, PublicKey publicKey) throws InvalidKeyException {
134         OpenSSLKey result = getOpenSSLKey(privateKey);
135         if (result != null) {
136             return result;
137         }
138 
139         result = fromKeyMaterial(privateKey);
140         if (result != null) {
141             return result;
142         }
143 
144         return wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey);
145     }
146 
147     /**
148      * Gets an {@code OpenSSLKey} instance backed by the provided EC private key. The resulting key
149      * is usable only by this provider's TLS/SSL stack.
150      *
151      * @param key private key.
152      * @param ecParams EC parameters {@code null} if not available. Some opaque private keys cannot
153      *        be used by the TLS/SSL stack without the parameters because the private key itself
154      *        might not expose the parameters.
155      */
fromECPrivateKeyForTLSStackOnly( PrivateKey key, ECParameterSpec ecParams)156     public static OpenSSLKey fromECPrivateKeyForTLSStackOnly(
157             PrivateKey key, ECParameterSpec ecParams) throws InvalidKeyException {
158         OpenSSLKey result = getOpenSSLKey(key);
159         if (result != null) {
160             return result;
161         }
162 
163         result = fromKeyMaterial(key);
164         if (result != null) {
165             return result;
166         }
167 
168         return OpenSSLECPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(key, ecParams);
169     }
170 
171     /**
172      * Gets the {@code OpenSSLKey} instance of the provided key.
173      *
174      * @return instance or {@code null} if the {@code key} is not backed by OpenSSL's
175      *         {@code EVP_PKEY}.
176      */
getOpenSSLKey(PrivateKey key)177     private static OpenSSLKey getOpenSSLKey(PrivateKey key) {
178         if (key instanceof OpenSSLKeyHolder) {
179             return ((OpenSSLKeyHolder) key).getOpenSSLKey();
180         }
181 
182         if ("RSA".equals(key.getAlgorithm())) {
183             return Platform.wrapRsaKey(key);
184         }
185 
186         return null;
187     }
188 
189     /**
190      * Gets an {@code OpenSSLKey} instance initialized with the key material of the provided key.
191      *
192      * @return instance or {@code null} if the {@code key} does not export its key material in a
193      *         suitable format.
194      */
fromKeyMaterial(PrivateKey key)195     private static OpenSSLKey fromKeyMaterial(PrivateKey key) {
196         if (!"PKCS#8".equals(key.getFormat())) {
197             return null;
198         }
199         byte[] encoded = key.getEncoded();
200         if (encoded == null) {
201             return null;
202         }
203         return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(encoded));
204     }
205 
206     /**
207      * Wraps the provided private key for use in the TLS/SSL stack only. Sign/decrypt operations
208      * using the key will be delegated to the {@code Signature}/{@code Cipher} implementation of the
209      * provider which accepts the key.
210      */
wrapJCAPrivateKeyForTLSStackOnly(PrivateKey privateKey, PublicKey publicKey)211     private static OpenSSLKey wrapJCAPrivateKeyForTLSStackOnly(PrivateKey privateKey,
212             PublicKey publicKey) throws InvalidKeyException {
213         String keyAlgorithm = privateKey.getAlgorithm();
214         if ("RSA".equals(keyAlgorithm)) {
215             return OpenSSLRSAPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey);
216         } else if ("EC".equals(keyAlgorithm)) {
217             return OpenSSLECPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(privateKey, publicKey);
218         } else {
219             throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm);
220         }
221     }
222 
wrapPrivateKey(PrivateKey key)223     private static OpenSSLKey wrapPrivateKey(PrivateKey key) throws InvalidKeyException {
224         if (key instanceof RSAPrivateKey) {
225             return OpenSSLRSAPrivateKey.wrapPlatformKey((RSAPrivateKey) key);
226         } else if (key instanceof ECPrivateKey) {
227             return OpenSSLECPrivateKey.wrapPlatformKey((ECPrivateKey) key);
228         } else {
229             throw new InvalidKeyException("Unknown key type: " + key.toString());
230         }
231     }
232 
fromPublicKey(PublicKey key)233     public static OpenSSLKey fromPublicKey(PublicKey key) throws InvalidKeyException {
234         if (key instanceof OpenSSLKeyHolder) {
235             return ((OpenSSLKeyHolder) key).getOpenSSLKey();
236         }
237 
238         if (!"X.509".equals(key.getFormat())) {
239             throw new InvalidKeyException("Unknown key format " + key.getFormat());
240         }
241 
242         final byte[] encoded = key.getEncoded();
243         if (encoded == null) {
244             throw new InvalidKeyException("Key encoding is null");
245         }
246 
247         try {
248             return new OpenSSLKey(NativeCrypto.d2i_PUBKEY(key.getEncoded()));
249         } catch (Exception e) {
250             throw new InvalidKeyException(e);
251         }
252     }
253 
254     /**
255      * Parse a public key in PEM encoding from the provided input stream.
256      *
257      * @throws InvalidKeyException if parsing fails
258      */
fromPublicKeyPemInputStream(InputStream is)259     public static OpenSSLKey fromPublicKeyPemInputStream(InputStream is)
260             throws InvalidKeyException {
261         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
262         try {
263             long keyCtx = NativeCrypto.PEM_read_bio_PUBKEY(bis.getBioContext());
264             if (keyCtx == 0L) {
265                 return null;
266             }
267 
268             return new OpenSSLKey(keyCtx);
269         } catch (Exception e) {
270             throw new InvalidKeyException(e);
271         } finally {
272             bis.release();
273         }
274     }
275 
getPublicKey()276     public PublicKey getPublicKey() throws NoSuchAlgorithmException {
277         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
278             case NativeConstants.EVP_PKEY_RSA:
279                 return new OpenSSLRSAPublicKey(this);
280             case NativeConstants.EVP_PKEY_EC:
281                 return new OpenSSLECPublicKey(this);
282             default:
283                 throw new NoSuchAlgorithmException("unknown PKEY type");
284         }
285     }
286 
getPublicKey(X509EncodedKeySpec keySpec, int type)287     static PublicKey getPublicKey(X509EncodedKeySpec keySpec, int type)
288             throws InvalidKeySpecException {
289         X509EncodedKeySpec x509KeySpec = keySpec;
290 
291         final OpenSSLKey key;
292         try {
293             key = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded()));
294         } catch (Exception e) {
295             throw new InvalidKeySpecException(e);
296         }
297 
298         if (NativeCrypto.EVP_PKEY_type(key.getNativeRef()) != type) {
299             throw new InvalidKeySpecException("Unexpected key type");
300         }
301 
302         try {
303             return key.getPublicKey();
304         } catch (NoSuchAlgorithmException e) {
305             throw new InvalidKeySpecException(e);
306         }
307     }
308 
getPrivateKey()309     public PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
310         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
311             case NativeConstants.EVP_PKEY_RSA:
312                 return new OpenSSLRSAPrivateKey(this);
313             case NativeConstants.EVP_PKEY_EC:
314                 return new OpenSSLECPrivateKey(this);
315             default:
316                 throw new NoSuchAlgorithmException("unknown PKEY type");
317         }
318     }
319 
getPrivateKey(PKCS8EncodedKeySpec keySpec, int type)320     static PrivateKey getPrivateKey(PKCS8EncodedKeySpec keySpec, int type)
321             throws InvalidKeySpecException {
322         PKCS8EncodedKeySpec pkcs8KeySpec = keySpec;
323 
324         final OpenSSLKey key;
325         try {
326             key = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded()));
327         } catch (Exception e) {
328             throw new InvalidKeySpecException(e);
329         }
330 
331         if (NativeCrypto.EVP_PKEY_type(key.getNativeRef()) != type) {
332             throw new InvalidKeySpecException("Unexpected key type");
333         }
334 
335         try {
336             return key.getPrivateKey();
337         } catch (NoSuchAlgorithmException e) {
338             throw new InvalidKeySpecException(e);
339         }
340     }
341 
342     @Override
equals(Object o)343     public boolean equals(Object o) {
344         if (o == this) {
345             return true;
346         }
347 
348         if (!(o instanceof OpenSSLKey)) {
349             return false;
350         }
351 
352         OpenSSLKey other = (OpenSSLKey) o;
353         if (ctx.equals(other.getNativeRef())) {
354             return true;
355         }
356 
357         /*
358          * ENGINE-based keys must be checked in a special way.
359          */
360         if (engine == null) {
361             if (other.getEngine() != null) {
362                 return false;
363             }
364         } else if (!engine.equals(other.getEngine())) {
365             return false;
366         } else {
367             if (alias != null) {
368                 return alias.equals(other.getAlias());
369             } else if (other.getAlias() != null) {
370                 return false;
371             }
372         }
373 
374         return NativeCrypto.EVP_PKEY_cmp(ctx, other.getNativeRef()) == 1;
375     }
376 
377     @Override
hashCode()378     public int hashCode() {
379         int hash = 1;
380         hash = hash * 17 + ctx.hashCode();
381         hash = hash * 31 + (int) (engine == null ? 0 : engine.getEngineContext());
382         return hash;
383     }
384 }
385