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