AndroidKeyStore.java revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.net;
6
7import android.util.Log;
8
9import org.chromium.base.CalledByNative;
10import org.chromium.base.JNINamespace;
11
12import java.lang.reflect.Method;
13import java.security.NoSuchAlgorithmException;
14import java.security.PrivateKey;
15import java.security.Signature;
16import java.security.interfaces.DSAKey;
17import java.security.interfaces.DSAParams;
18import java.security.interfaces.DSAPrivateKey;
19import java.security.interfaces.ECKey;
20import java.security.interfaces.ECPrivateKey;
21import java.security.interfaces.RSAKey;
22import java.security.interfaces.RSAPrivateKey;
23import java.security.spec.ECParameterSpec;
24
25@JNINamespace("net::android")
26public class AndroidKeyStore {
27
28    private static final String TAG = "AndroidKeyStore";
29
30    ////////////////////////////////////////////////////////////////////
31    //
32    // Message signing support.
33
34    /**
35     * Returns the public modulus of a given RSA private key as a byte
36     * buffer.
37     * This can be used by native code to convert the modulus into
38     * an OpenSSL BIGNUM object. Required to craft a custom native RSA
39     * object where RSA_size() works as expected.
40     *
41     * @param key A PrivateKey instance, must implement RSAKey.
42     * @return A byte buffer corresponding to the modulus. This is
43     * big-endian representation of a BigInteger.
44     */
45    @CalledByNative
46    public static byte[] getRSAKeyModulus(PrivateKey key) {
47        if (key instanceof RSAKey) {
48            return ((RSAKey) key).getModulus().toByteArray();
49        } else {
50            Log.w(TAG, "Not a RSAKey instance!");
51            return null;
52        }
53    }
54
55    /**
56     * Returns the 'Q' parameter of a given DSA private key as a byte
57     * buffer.
58     * This can be used by native code to convert it into an OpenSSL BIGNUM
59     * object where DSA_size() works as expected.
60     *
61     * @param key A PrivateKey instance. Must implement DSAKey.
62     * @return A byte buffer corresponding to the Q parameter. This is
63     * a big-endian representation of a BigInteger.
64     */
65    @CalledByNative
66    public static byte[] getDSAKeyParamQ(PrivateKey key) {
67        if (key instanceof DSAKey) {
68            DSAParams params = ((DSAKey) key).getParams();
69            return params.getQ().toByteArray();
70        } else {
71            Log.w(TAG, "Not a DSAKey instance!");
72            return null;
73        }
74    }
75
76    /**
77     * Returns the 'order' parameter of a given ECDSA private key as a
78     * a byte buffer.
79     * @param key A PrivateKey instance. Must implement ECKey.
80     * @return A byte buffer corresponding to the 'order' parameter.
81     * This is a big-endian representation of a BigInteger.
82     */
83    @CalledByNative
84    public static byte[] getECKeyOrder(PrivateKey key) {
85        if (key instanceof ECKey) {
86            ECParameterSpec params = ((ECKey) key).getParams();
87            return params.getOrder().toByteArray();
88        } else {
89            Log.w(TAG, "Not an ECKey instance!");
90            return null;
91        }
92    }
93
94    /**
95     * Returns the encoded data corresponding to a given PrivateKey.
96     * Note that this will fail for platform keys on Android 4.0.4
97     * and higher. It can be used on 4.0.3 and older platforms to
98     * route around the platform bug described below.
99     * @param key A PrivateKey instance
100     * @return encoded key as PKCS#8 byte array, can be null.
101     */
102    @CalledByNative
103    public static byte[] getPrivateKeyEncodedBytes(PrivateKey key) {
104        return key.getEncoded();
105    }
106
107    /**
108     * Sign a given message with a given PrivateKey object. This method
109     * shall only be used to implement signing in the context of SSL
110     * client certificate support.
111     *
112     * The message will actually be a hash, computed by OpenSSL itself,
113     * depending on the type of the key. The result should match exactly
114     * what the vanilla implementations of the following OpenSSL function
115     * calls do:
116     *
117     *  - For a RSA private key, this should be equivalent to calling
118     *    RSA_private_encrypt(..., RSA_PKCS1_PADDING), i.e. it must
119     *    generate a raw RSA signature. The message must be either a
120     *    combined, 36-byte MD5+SHA1 message digest or a DigestInfo
121     *    value wrapping a message digest.
122     *
123     *  - For a DSA and ECDSA private keys, this should be equivalent to
124     *    calling DSA_sign(0,...) and ECDSA_sign(0,...) respectively. The
125     *    message must be a hash and the function shall compute a direct
126     *    DSA/ECDSA signature for it.
127     *
128     * @param privateKey The PrivateKey handle.
129     * @param message The message to sign.
130     * @return signature as a byte buffer.
131     *
132     * Important: Due to a platform bug, this function will always fail on
133     *            Android < 4.2 for RSA PrivateKey objects. See the
134     *            getOpenSSLHandleForPrivateKey() below for work-around.
135     */
136    @CalledByNative
137    public static byte[] rawSignDigestWithPrivateKey(PrivateKey privateKey,
138                                                     byte[] message) {
139        // Get the Signature for this key.
140        Signature signature = null;
141        // Hint: Algorithm names come from:
142        // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
143        try {
144            if (privateKey instanceof RSAPrivateKey) {
145                // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException
146                // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
147                // See https://android-review.googlesource.com/#/c/40352/
148                signature = Signature.getInstance("NONEwithRSA");
149            } else if (privateKey instanceof DSAPrivateKey) {
150                signature = Signature.getInstance("NONEwithDSA");
151            } else if (privateKey instanceof ECPrivateKey) {
152                signature = Signature.getInstance("NONEwithECDSA");
153            }
154        } catch (NoSuchAlgorithmException e) {
155            ;
156        }
157
158        if (signature == null) {
159            Log.e(TAG, "Unsupported private key algorithm: " + privateKey.getAlgorithm());
160            return null;
161        }
162
163        // Sign the message.
164        try {
165            signature.initSign(privateKey);
166            signature.update(message);
167            return signature.sign();
168        } catch (Exception e) {
169            Log.e(TAG, "Exception while signing message with " + privateKey.getAlgorithm() +
170                        " private key: " + e);
171            return null;
172        }
173    }
174
175    /**
176     * Return the type of a given PrivateKey object. This is an integer
177     * that maps to one of the values defined by org.chromium.net.PrivateKeyType,
178     * which is itself auto-generated from net/android/private_key_type_list.h
179     * @param privateKey The PrivateKey handle
180     * @return key type, or PrivateKeyType.INVALID if unknown.
181     */
182    @CalledByNative
183    public static int getPrivateKeyType(PrivateKey privateKey) {
184        if (privateKey instanceof RSAPrivateKey)
185            return PrivateKeyType.RSA;
186        if (privateKey instanceof DSAPrivateKey)
187            return PrivateKeyType.DSA;
188        if (privateKey instanceof ECPrivateKey)
189            return PrivateKeyType.ECDSA;
190        else
191            return PrivateKeyType.INVALID;
192    }
193
194    /**
195     * Return the system EVP_PKEY handle corresponding to a given PrivateKey
196     * object, obtained through reflection.
197     *
198     * This shall only be used when the "NONEwithRSA" signature is not
199     * available, as described in rawSignDigestWithPrivateKey(). I.e.
200     * never use this on Android 4.2 or higher.
201     *
202     * This can only work in Android 4.0.4 and higher, for older versions
203     * of the platform (e.g. 4.0.3), there is no system OpenSSL EVP_PKEY,
204     * but the private key contents can be retrieved directly with
205     * the getEncoded() method.
206     *
207     * This assumes that the target device uses a vanilla AOSP
208     * implementation of its java.security classes, which is also
209     * based on OpenSSL (fortunately, no OEM has apperently changed to
210     * a different implementation, according to the Android team).
211     *
212     * Note that the object returned was created with the platform version
213     * of OpenSSL, and _not_ the one that comes with Chromium. Whether the
214     * object can be used safely with the Chromium OpenSSL library depends
215     * on differences between their actual ABI / implementation details.
216     *
217     * To better understand what's going on below, please refer to the
218     * following source files in the Android 4.0.4 and 4.1 source trees:
219     * libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
220     * libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
221     *
222     * @param privateKey The PrivateKey handle.
223     * @return The EVP_PKEY handle, as a 32-bit integer (0 if not available)
224     */
225    @CalledByNative
226    public static int getOpenSSLHandleForPrivateKey(PrivateKey privateKey) {
227        // Sanity checks
228        if (privateKey == null) {
229            Log.e(TAG, "privateKey == null");
230            return 0;
231        }
232        if (!(privateKey instanceof RSAPrivateKey)) {
233            Log.e(TAG, "does not implement RSAPrivateKey");
234            return 0;
235        }
236        // First, check that this is a proper instance of OpenSSLRSAPrivateKey
237        // or one of its sub-classes.
238        Class<?> superClass;
239        try {
240            superClass = Class.forName(
241                    "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
242        } catch (Exception e) {
243            // This may happen if the target device has a completely different
244            // implementation of the java.security APIs, compared to vanilla
245            // Android. Highly unlikely, but still possible.
246            Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
247            return 0;
248        }
249        if (!superClass.isInstance(privateKey)) {
250            // This may happen if the PrivateKey was not created by the "AndroidOpenSSL"
251            // provider, which should be the default. That could happen if an OEM decided
252            // to implement a different default provider. Also highly unlikely.
253            Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" +
254                       privateKey.getClass().getCanonicalName());
255            return 0;
256        }
257
258        try {
259            // Use reflection to invoke the 'getOpenSSLKey()' method on
260            // the private key. This returns another Java object that wraps
261            // a native EVP_PKEY. Note that the method is final, so calling
262            // the superclass implementation is ok.
263            Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
264            getKey.setAccessible(true);
265            Object opensslKey = null;
266            try {
267                opensslKey = getKey.invoke(privateKey);
268            } finally {
269                getKey.setAccessible(false);
270            }
271            if (opensslKey == null) {
272                // Bail when detecting OEM "enhancement".
273                Log.e(TAG, "getOpenSSLKey() returned null");
274                return 0;
275            }
276
277            // Use reflection to invoke the 'getPkeyContext' method on the
278            // result of the getOpenSSLKey(). This is an 32-bit integer
279            // which is the address of an EVP_PKEY object.
280            Method getPkeyContext;
281            try {
282                getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
283            } catch (Exception e) {
284                // Bail here too, something really not working as expected.
285                Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
286                return 0;
287            }
288            getPkeyContext.setAccessible(true);
289            int evp_pkey = 0;
290            try {
291                evp_pkey = (Integer) getPkeyContext.invoke(opensslKey);
292            } finally {
293                getPkeyContext.setAccessible(false);
294            }
295            if (evp_pkey == 0) {
296                // The PrivateKey is probably rotten for some reason.
297                Log.e(TAG, "getPkeyContext() returned null");
298            }
299            return evp_pkey;
300
301        } catch (Exception e) {
302            Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
303            return 0;
304        }
305    }
306}
307