AndroidKeyStore.java revision 58537e28ecd584eab876aee8be7156509866d23a
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 by OpenSSL itself,
116     * depending on the type of the key. The result should match exactly
117     * what the vanilla implementations of the following OpenSSL function
118     * calls do:
119     *
120     *  - For a RSA private key, this should be equivalent to calling
121     *    RSA_private_encrypt(..., RSA_PKCS1_PADDING), i.e. it must
122     *    generate a raw RSA signature. The message must be either a
123     *    combined, 36-byte MD5+SHA1 message digest or a DigestInfo
124     *    value wrapping a message digest.
125     *
126     *  - For a DSA and ECDSA private keys, this should be equivalent to
127     *    calling DSA_sign(0,...) and ECDSA_sign(0,...) respectively. The
128     *    message must be a hash and the function shall compute a direct
129     *    DSA/ECDSA signature for it.
130     *
131     * @param privateKey The PrivateKey handle.
132     * @param message The message to sign.
133     * @return signature as a byte buffer.
134     *
135     * Important: Due to a platform bug, this function will always fail on
136     *            Android < 4.2 for RSA PrivateKey objects. See the
137     *            getOpenSSLHandleForPrivateKey() below for work-around.
138     */
139    @CalledByNative
140    public static byte[] rawSignDigestWithPrivateKey(PrivateKey privateKey,
141                                                     byte[] message) {
142        // Get the Signature for this key.
143        Signature signature = null;
144        // Hint: Algorithm names come from:
145        // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
146        try {
147            if (privateKey instanceof RSAPrivateKey) {
148                // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException
149                // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
150                // See https://android-review.googlesource.com/#/c/40352/
151                signature = Signature.getInstance("NONEwithRSA");
152            } else if (privateKey instanceof DSAPrivateKey) {
153                signature = Signature.getInstance("NONEwithDSA");
154            } else if (privateKey instanceof ECPrivateKey) {
155                signature = Signature.getInstance("NONEwithECDSA");
156            }
157        } catch (NoSuchAlgorithmException e) {
158            ;
159        }
160
161        if (signature == null) {
162            Log.e(TAG, "Unsupported private key algorithm: " + privateKey.getAlgorithm());
163            return null;
164        }
165
166        // Sign the message.
167        try {
168            signature.initSign(privateKey);
169            signature.update(message);
170            return signature.sign();
171        } catch (Exception e) {
172            Log.e(TAG, "Exception while signing message with " + privateKey.getAlgorithm() +
173                        " private key: " + e);
174            return null;
175        }
176    }
177
178    /**
179     * Return the type of a given PrivateKey object. This is an integer
180     * that maps to one of the values defined by org.chromium.net.PrivateKeyType,
181     * which is itself auto-generated from net/android/private_key_type_list.h
182     * @param privateKey The PrivateKey handle
183     * @return key type, or PrivateKeyType.INVALID if unknown.
184     */
185    @CalledByNative
186    public static int getPrivateKeyType(PrivateKey privateKey) {
187        if (privateKey instanceof RSAPrivateKey)
188            return PrivateKeyType.RSA;
189        if (privateKey instanceof DSAPrivateKey)
190            return PrivateKeyType.DSA;
191        if (privateKey instanceof ECPrivateKey)
192            return PrivateKeyType.ECDSA;
193        else
194            return PrivateKeyType.INVALID;
195    }
196
197    /**
198     * Return the system EVP_PKEY handle corresponding to a given PrivateKey
199     * object, obtained through reflection.
200     *
201     * This shall only be used when the "NONEwithRSA" signature is not
202     * available, as described in rawSignDigestWithPrivateKey(). I.e.
203     * never use this on Android 4.2 or higher.
204     *
205     * This can only work in Android 4.0.4 and higher, for older versions
206     * of the platform (e.g. 4.0.3), there is no system OpenSSL EVP_PKEY,
207     * but the private key contents can be retrieved directly with
208     * the getEncoded() method.
209     *
210     * This assumes that the target device uses a vanilla AOSP
211     * implementation of its java.security classes, which is also
212     * based on OpenSSL (fortunately, no OEM has apperently changed to
213     * a different implementation, according to the Android team).
214     *
215     * Note that the object returned was created with the platform version
216     * of OpenSSL, and _not_ the one that comes with Chromium. Whether the
217     * object can be used safely with the Chromium OpenSSL library depends
218     * on differences between their actual ABI / implementation details.
219     *
220     * To better understand what's going on below, please refer to the
221     * following source files in the Android 4.0.4 and 4.1 source trees:
222     * libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
223     * libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
224     *
225     * @param privateKey The PrivateKey handle.
226     * @return The EVP_PKEY handle, as a 32-bit integer (0 if not available)
227     */
228    @CalledByNative
229    public static int getOpenSSLHandleForPrivateKey(PrivateKey privateKey) {
230        // Sanity checks
231        if (privateKey == null) {
232            Log.e(TAG, "privateKey == null");
233            return 0;
234        }
235        if (!(privateKey instanceof RSAPrivateKey)) {
236            Log.e(TAG, "does not implement RSAPrivateKey");
237            return 0;
238        }
239        // First, check that this is a proper instance of OpenSSLRSAPrivateKey
240        // or one of its sub-classes.
241        Class<?> superClass;
242        try {
243            superClass = Class.forName(
244                    "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
245        } catch (Exception e) {
246            // This may happen if the target device has a completely different
247            // implementation of the java.security APIs, compared to vanilla
248            // Android. Highly unlikely, but still possible.
249            Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
250            return 0;
251        }
252        if (!superClass.isInstance(privateKey)) {
253            // This may happen if the PrivateKey was not created by the "AndroidOpenSSL"
254            // provider, which should be the default. That could happen if an OEM decided
255            // to implement a different default provider. Also highly unlikely.
256            Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" +
257                       privateKey.getClass().getCanonicalName());
258            return 0;
259        }
260
261        try {
262            // Use reflection to invoke the 'getOpenSSLKey()' method on
263            // the private key. This returns another Java object that wraps
264            // a native EVP_PKEY. Note that the method is final, so calling
265            // the superclass implementation is ok.
266            Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
267            getKey.setAccessible(true);
268            Object opensslKey = null;
269            try {
270                opensslKey = getKey.invoke(privateKey);
271            } finally {
272                getKey.setAccessible(false);
273            }
274            if (opensslKey == null) {
275                // Bail when detecting OEM "enhancement".
276                Log.e(TAG, "getOpenSSLKey() returned null");
277                return 0;
278            }
279
280            // Use reflection to invoke the 'getPkeyContext' method on the
281            // result of the getOpenSSLKey(). This is an 32-bit integer
282            // which is the address of an EVP_PKEY object.
283            Method getPkeyContext;
284            try {
285                getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
286            } catch (Exception e) {
287                // Bail here too, something really not working as expected.
288                Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
289                return 0;
290            }
291            getPkeyContext.setAccessible(true);
292            int evp_pkey = 0;
293            try {
294                evp_pkey = (Integer) getPkeyContext.invoke(opensslKey);
295            } finally {
296                getPkeyContext.setAccessible(false);
297            }
298            if (evp_pkey == 0) {
299                // The PrivateKey is probably rotten for some reason.
300                Log.e(TAG, "getPkeyContext() returned null");
301            }
302            return evp_pkey;
303
304        } catch (Exception e) {
305            Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
306            return 0;
307        }
308    }
309}
310