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.security.InvalidKeyException;
20import java.security.NoSuchAlgorithmException;
21import java.security.PrivateKey;
22import java.security.PublicKey;
23import java.security.spec.InvalidKeySpecException;
24import java.security.spec.PKCS8EncodedKeySpec;
25import java.security.spec.X509EncodedKeySpec;
26import javax.crypto.SecretKey;
27
28public class OpenSSLKey {
29    private final long ctx;
30
31    private final OpenSSLEngine engine;
32
33    private final String alias;
34
35    public OpenSSLKey(long ctx) {
36        this.ctx = ctx;
37        engine = null;
38        alias = null;
39    }
40
41    public OpenSSLKey(long ctx, OpenSSLEngine engine, String alias) {
42        this.ctx = ctx;
43        this.engine = engine;
44        this.alias = alias;
45    }
46
47    /**
48     * Returns the raw pointer to the EVP_PKEY context for use in JNI calls. The
49     * life cycle of this native pointer is managed by the {@code OpenSSLKey}
50     * instance and must not be destroyed or freed by users of this API.
51     */
52    public long getPkeyContext() {
53        return ctx;
54    }
55
56    OpenSSLEngine getEngine() {
57        return engine;
58    }
59
60    boolean isEngineBased() {
61        return engine != null;
62    }
63
64    public String getAlias() {
65        return alias;
66    }
67
68    public static OpenSSLKey fromPrivateKey(PrivateKey key) throws InvalidKeyException {
69        if (key instanceof OpenSSLKeyHolder) {
70            return ((OpenSSLKeyHolder) key).getOpenSSLKey();
71        }
72
73        if ("PKCS#8".equals(key.getFormat())) {
74            return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(key.getEncoded()));
75        } else {
76            throw new InvalidKeyException("Unknown key format " + key.getFormat());
77        }
78    }
79
80    public PublicKey getPublicKey() throws NoSuchAlgorithmException {
81        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
82            case NativeCrypto.EVP_PKEY_RSA:
83                return new OpenSSLRSAPublicKey(this);
84            case NativeCrypto.EVP_PKEY_DSA:
85                return new OpenSSLDSAPublicKey(this);
86            case NativeCrypto.EVP_PKEY_EC:
87                return new OpenSSLECPublicKey(this);
88            default:
89                throw new NoSuchAlgorithmException("unknown PKEY type");
90        }
91    }
92
93    static PublicKey getPublicKey(X509EncodedKeySpec keySpec, int type)
94            throws InvalidKeySpecException {
95        X509EncodedKeySpec x509KeySpec = (X509EncodedKeySpec) keySpec;
96
97        final OpenSSLKey key;
98        try {
99            key = new OpenSSLKey(NativeCrypto.d2i_PUBKEY(x509KeySpec.getEncoded()));
100        } catch (Exception e) {
101            throw new InvalidKeySpecException(e);
102        }
103
104        if (NativeCrypto.EVP_PKEY_type(key.getPkeyContext()) != type) {
105            throw new InvalidKeySpecException("Unexpected key type");
106        }
107
108        try {
109            return key.getPublicKey();
110        } catch (NoSuchAlgorithmException e) {
111            throw new InvalidKeySpecException(e);
112        }
113    }
114
115    public PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
116        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
117            case NativeCrypto.EVP_PKEY_RSA:
118                return new OpenSSLRSAPrivateKey(this);
119            case NativeCrypto.EVP_PKEY_DSA:
120                return new OpenSSLDSAPrivateKey(this);
121            case NativeCrypto.EVP_PKEY_EC:
122                return new OpenSSLECPrivateKey(this);
123            default:
124                throw new NoSuchAlgorithmException("unknown PKEY type");
125        }
126    }
127
128    static PrivateKey getPrivateKey(PKCS8EncodedKeySpec keySpec, int type)
129            throws InvalidKeySpecException {
130        PKCS8EncodedKeySpec pkcs8KeySpec = (PKCS8EncodedKeySpec) keySpec;
131
132        final OpenSSLKey key;
133        try {
134            key = new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(pkcs8KeySpec.getEncoded()));
135        } catch (Exception e) {
136            throw new InvalidKeySpecException(e);
137        }
138
139        if (NativeCrypto.EVP_PKEY_type(key.getPkeyContext()) != type) {
140            throw new InvalidKeySpecException("Unexpected key type");
141        }
142
143        try {
144            return key.getPrivateKey();
145        } catch (NoSuchAlgorithmException e) {
146            throw new InvalidKeySpecException(e);
147        }
148    }
149
150    public SecretKey getSecretKey(String algorithm) throws NoSuchAlgorithmException {
151        switch (NativeCrypto.EVP_PKEY_type(ctx)) {
152            case NativeCrypto.EVP_PKEY_HMAC:
153            case NativeCrypto.EVP_PKEY_CMAC:
154                return new OpenSSLSecretKey(algorithm, this);
155            default:
156                throw new NoSuchAlgorithmException("unknown PKEY type");
157        }
158    }
159
160    @Override
161    protected void finalize() throws Throwable {
162        try {
163            if (ctx != 0) {
164                NativeCrypto.EVP_PKEY_free(ctx);
165            }
166        } finally {
167            super.finalize();
168        }
169    }
170
171    @Override
172    public boolean equals(Object o) {
173        if (o == this) {
174            return true;
175        }
176
177        if (!(o instanceof OpenSSLKey)) {
178            return false;
179        }
180
181        OpenSSLKey other = (OpenSSLKey) o;
182        if (ctx == other.getPkeyContext()) {
183            return true;
184        }
185
186        /*
187         * ENGINE-based keys must be checked in a special way.
188         */
189        if (engine == null) {
190            if (other.getEngine() != null) {
191                return false;
192            }
193        } else if (!engine.equals(other.getEngine())) {
194            return false;
195        } else {
196            if (alias != null) {
197                return alias.equals(other.getAlias());
198            } else if (other.getAlias() != null) {
199                return false;
200            }
201        }
202
203        return NativeCrypto.EVP_PKEY_cmp(ctx, other.getPkeyContext()) == 1;
204    }
205
206    @Override
207    public int hashCode() {
208        int hash = 1;
209        hash = hash * 17 + (int) ctx;
210        hash = hash * 31 + (int) (engine == null ? 0 : engine.getEngineContext());
211        return hash;
212    }
213}
214