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    static OpenSSLKey getInstance(RSAPrivateKey rsaPrivateKey) throws InvalidKeyException {
93        final BigInteger modulus = rsaPrivateKey.getModulus();
94        final BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
95
96        if (modulus == null) {
97            throw new InvalidKeyException("modulus == null");
98        } else if (privateExponent == null) {
99            throw new InvalidKeyException("privateExponent == null");
100        }
101
102        try {
103            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
104                    modulus.toByteArray(),
105                    null,
106                    privateExponent.toByteArray(),
107                    null,
108                    null,
109                    null,
110                    null,
111                    null));
112        } catch (Exception e) {
113            throw new InvalidKeyException(e);
114        }
115    }
116
117    synchronized final void ensureReadParams() {
118        if (fetchedParams) {
119            return;
120        }
121        readParams(NativeCrypto.get_RSA_private_params(key.getPkeyContext()));
122        fetchedParams = true;
123    }
124
125    void readParams(byte[][] params) {
126        if (params[0] == null) {
127            throw new NullPointerException("modulus == null");
128        } else if (params[2] == null && !key.isEngineBased()) {
129            throw new NullPointerException("privateExponent == null");
130        }
131
132        modulus = new BigInteger(params[0]);
133
134        // ENGINE-based keys are not guaranteed to have a private exponent.
135        if (params[2] != null) {
136            privateExponent = new BigInteger(params[2]);
137        }
138    }
139
140    @Override
141    public final BigInteger getPrivateExponent() {
142        if (key.isEngineBased()) {
143            throw new UnsupportedOperationException("private exponent cannot be extracted");
144        }
145
146        ensureReadParams();
147        return privateExponent;
148    }
149
150    @Override
151    public final BigInteger getModulus() {
152        ensureReadParams();
153        return modulus;
154    }
155
156    @Override
157    public final byte[] getEncoded() {
158        /*
159         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
160         * the key. Returning {@code null} tells the caller that there's no
161         * encoded format.
162         */
163        if (key.isEngineBased()) {
164            return null;
165        }
166
167        return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
168    }
169
170    public final String getFormat() {
171        /*
172         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
173         * the key. Returning {@code null} tells the caller that there's no
174         * encoded format.
175         */
176        if (key.isEngineBased()) {
177            return null;
178        }
179
180        return "PKCS#8";
181    }
182
183    @Override
184    public final String getAlgorithm() {
185        return "RSA";
186    }
187
188    @Override
189    public boolean equals(Object o) {
190        if (o == this) {
191            return true;
192        }
193
194        if (o instanceof OpenSSLRSAPrivateKey) {
195            OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
196            return key.equals(other.getOpenSSLKey());
197        }
198
199        if (o instanceof RSAPrivateKey) {
200            ensureReadParams();
201            RSAPrivateKey other = (RSAPrivateKey) o;
202
203            return modulus.equals(other.getModulus())
204                    && privateExponent.equals(other.getPrivateExponent());
205        }
206
207        return false;
208    }
209
210    @Override
211    public int hashCode() {
212        ensureReadParams();
213        int hash = 1;
214
215        hash = hash * 3 + modulus.hashCode();
216        if (privateExponent != null) {
217            hash = hash * 7 + privateExponent.hashCode();
218        }
219
220        return hash;
221    }
222
223    @Override
224    public String toString() {
225        final StringBuilder sb = new StringBuilder("OpenSSLRSAPrivateKey{");
226
227        final boolean engineBased = key.isEngineBased();
228        if (engineBased) {
229            sb.append("key=");
230            sb.append(key);
231            sb.append('}');
232        }
233
234        ensureReadParams();
235        sb.append("modulus=");
236        sb.append(modulus.toString(16));
237        sb.append(',');
238
239        if (!engineBased) {
240            sb.append("privateExponent=");
241            sb.append(privateExponent.toString(16));
242            sb.append(',');
243        }
244
245        return sb.toString();
246    }
247
248    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
249        stream.defaultReadObject();
250
251        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
252                modulus.toByteArray(),
253                null,
254                privateExponent.toByteArray(),
255                null,
256                null,
257                null,
258                null,
259                null));
260        fetchedParams = true;
261    }
262
263    private void writeObject(ObjectOutputStream stream) throws IOException {
264        if (getOpenSSLKey().isEngineBased()) {
265            throw new NotSerializableException("engine-based keys can not be serialized");
266        }
267
268        ensureReadParams();
269        stream.defaultWriteObject();
270    }
271}
272