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.IOException;
20import java.io.NotSerializableException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.math.BigInteger;
24import java.security.InvalidKeyException;
25import java.security.interfaces.RSAPrivateKey;
26import java.security.spec.InvalidKeySpecException;
27import java.security.spec.RSAPrivateKeySpec;
28
29public class OpenSSLRSAPrivateKey implements RSAPrivateKey, OpenSSLKeyHolder {
30    private static final long serialVersionUID = 4872170254439578735L;
31
32    protected transient OpenSSLKey key;
33
34    protected transient boolean fetchedParams;
35
36    protected BigInteger modulus;
37
38    protected BigInteger privateExponent;
39
40    OpenSSLRSAPrivateKey(OpenSSLKey key) {
41        this.key = key;
42    }
43
44    OpenSSLRSAPrivateKey(OpenSSLKey key, byte[][] params) {
45        this(key);
46        readParams(params);
47        fetchedParams = true;
48    }
49
50    @Override
51    public OpenSSLKey getOpenSSLKey() {
52        return key;
53    }
54
55    public OpenSSLRSAPrivateKey(RSAPrivateKeySpec rsaKeySpec) throws InvalidKeySpecException {
56        this(init(rsaKeySpec));
57    }
58
59    private static OpenSSLKey init(RSAPrivateKeySpec rsaKeySpec) throws InvalidKeySpecException {
60        final BigInteger modulus = rsaKeySpec.getModulus();
61        final BigInteger privateExponent = rsaKeySpec.getPrivateExponent();
62
63        if (modulus == null) {
64            throw new InvalidKeySpecException("modulus == null");
65        } else if (privateExponent == null) {
66            throw new InvalidKeySpecException("privateExponent == null");
67        }
68
69        try {
70            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
71                    modulus.toByteArray(),
72                    null,
73                    privateExponent.toByteArray(),
74                    null,
75                    null,
76                    null,
77                    null,
78                    null));
79        } catch (Exception e) {
80            throw new InvalidKeySpecException(e);
81        }
82    }
83
84    static OpenSSLRSAPrivateKey getInstance(OpenSSLKey key) {
85      byte[][] params = NativeCrypto.get_RSA_private_params(key.getPkeyContext());
86      if (params[1] != null) {
87          return new OpenSSLRSAPrivateCrtKey(key, params);
88      }
89      return new OpenSSLRSAPrivateKey(key, params);
90    }
91
92    protected static OpenSSLKey wrapPlatformKey(RSAPrivateKey rsaPrivateKey)
93            throws InvalidKeyException {
94        OpenSSLKey wrapper = Platform.wrapRsaKey(rsaPrivateKey);
95        if (wrapper != null) {
96            return wrapper;
97        }
98        return new OpenSSLKey(NativeCrypto.getRSAPrivateKeyWrapper(rsaPrivateKey, rsaPrivateKey
99                .getModulus().toByteArray()), true);
100    }
101
102    static OpenSSLKey getInstance(RSAPrivateKey rsaPrivateKey) throws InvalidKeyException {
103        /**
104         * If the key is not encodable (PKCS11-like key), then wrap it and use
105         * JNI upcalls to satisfy requests.
106         */
107        if (rsaPrivateKey.getFormat() == null) {
108            return wrapPlatformKey(rsaPrivateKey);
109        }
110
111        final BigInteger modulus = rsaPrivateKey.getModulus();
112        final BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
113
114        if (modulus == null) {
115            throw new InvalidKeyException("modulus == null");
116        } else if (privateExponent == null) {
117            throw new InvalidKeyException("privateExponent == null");
118        }
119
120        try {
121            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
122                    modulus.toByteArray(),
123                    null,
124                    privateExponent.toByteArray(),
125                    null,
126                    null,
127                    null,
128                    null,
129                    null));
130        } catch (Exception e) {
131            throw new InvalidKeyException(e);
132        }
133    }
134
135    synchronized final void ensureReadParams() {
136        if (fetchedParams) {
137            return;
138        }
139        readParams(NativeCrypto.get_RSA_private_params(key.getPkeyContext()));
140        fetchedParams = true;
141    }
142
143    void readParams(byte[][] params) {
144        if (params[0] == null) {
145            throw new NullPointerException("modulus == null");
146        } else if (params[2] == null && !key.isEngineBased()) {
147            throw new NullPointerException("privateExponent == null");
148        }
149
150        modulus = new BigInteger(params[0]);
151
152        // ENGINE-based keys are not guaranteed to have a private exponent.
153        if (params[2] != null) {
154            privateExponent = new BigInteger(params[2]);
155        }
156    }
157
158    @Override
159    public final BigInteger getPrivateExponent() {
160        if (key.isEngineBased()) {
161            throw new UnsupportedOperationException("private exponent cannot be extracted");
162        }
163
164        ensureReadParams();
165        return privateExponent;
166    }
167
168    @Override
169    public final BigInteger getModulus() {
170        ensureReadParams();
171        return modulus;
172    }
173
174    @Override
175    public final byte[] getEncoded() {
176        /*
177         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
178         * the key. Returning {@code null} tells the caller that there's no
179         * encoded format.
180         */
181        if (key.isEngineBased()) {
182            return null;
183        }
184
185        return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
186    }
187
188    @Override
189    public final String getFormat() {
190        /*
191         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
192         * the key. Returning {@code null} tells the caller that there's no
193         * encoded format.
194         */
195        if (key.isEngineBased()) {
196            return null;
197        }
198
199        return "PKCS#8";
200    }
201
202    @Override
203    public final String getAlgorithm() {
204        return "RSA";
205    }
206
207    @Override
208    public boolean equals(Object o) {
209        if (o == this) {
210            return true;
211        }
212
213        if (o instanceof OpenSSLRSAPrivateKey) {
214            OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
215            return key.equals(other.getOpenSSLKey());
216        }
217
218        if (o instanceof RSAPrivateKey) {
219            ensureReadParams();
220            RSAPrivateKey other = (RSAPrivateKey) o;
221
222            return modulus.equals(other.getModulus())
223                    && privateExponent.equals(other.getPrivateExponent());
224        }
225
226        return false;
227    }
228
229    @Override
230    public int hashCode() {
231        ensureReadParams();
232        int hash = 1;
233
234        hash = hash * 3 + modulus.hashCode();
235        if (privateExponent != null) {
236            hash = hash * 7 + privateExponent.hashCode();
237        }
238
239        return hash;
240    }
241
242    @Override
243    public String toString() {
244        final StringBuilder sb = new StringBuilder("OpenSSLRSAPrivateKey{");
245
246        final boolean engineBased = key.isEngineBased();
247        if (engineBased) {
248            sb.append("key=");
249            sb.append(key);
250            sb.append('}');
251        }
252
253        ensureReadParams();
254        sb.append("modulus=");
255        sb.append(modulus.toString(16));
256        sb.append(',');
257
258        if (!engineBased) {
259            sb.append("privateExponent=");
260            sb.append(privateExponent.toString(16));
261            sb.append(',');
262        }
263
264        return sb.toString();
265    }
266
267    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
268        stream.defaultReadObject();
269
270        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
271                modulus.toByteArray(),
272                null,
273                privateExponent.toByteArray(),
274                null,
275                null,
276                null,
277                null,
278                null));
279        fetchedParams = true;
280    }
281
282    private void writeObject(ObjectOutputStream stream) throws IOException {
283        if (getOpenSSLKey().isEngineBased()) {
284            throw new NotSerializableException("engine-based keys can not be serialized");
285        }
286
287        ensureReadParams();
288        stream.defaultWriteObject();
289    }
290}
291