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