DefaultAndroidKeyStore.java revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 java.lang.reflect.Method;
10import java.security.NoSuchAlgorithmException;
11import java.security.PrivateKey;
12import java.security.Signature;
13import java.security.interfaces.DSAKey;
14import java.security.interfaces.DSAParams;
15import java.security.interfaces.DSAPrivateKey;
16import java.security.interfaces.ECKey;
17import java.security.interfaces.ECPrivateKey;
18import java.security.interfaces.RSAKey;
19import java.security.interfaces.RSAPrivateKey;
20import java.security.spec.ECParameterSpec;
21
22/**
23 * Simple implementation of the AndroidKeyStore for use with an in-process Java KeyStore.
24 */
25public class DefaultAndroidKeyStore implements AndroidKeyStore {
26
27    private static final String TAG = "AndroidKeyStoreInProcessImpl";
28
29    private static class DefaultAndroidPrivateKey implements AndroidPrivateKey {
30        // The actual Java key being wrapped.
31        final PrivateKey mKey;
32        // Key store handling this key.
33        final DefaultAndroidKeyStore mStore;
34
35        DefaultAndroidPrivateKey(PrivateKey key, DefaultAndroidKeyStore store) {
36            mKey = key;
37            mStore = store;
38        }
39
40        PrivateKey getJavaKey() {
41            return mKey;
42        }
43
44        @Override
45        public AndroidKeyStore getKeyStore() {
46            return mStore;
47        }
48    }
49
50    public AndroidPrivateKey createKey(PrivateKey javaKey) {
51        return new DefaultAndroidPrivateKey(javaKey, this);
52    }
53
54    @Override
55    public byte[] getRSAKeyModulus(AndroidPrivateKey key) {
56        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
57        if (javaKey instanceof RSAKey) {
58            return ((RSAKey) javaKey).getModulus().toByteArray();
59        }
60        Log.w(TAG, "Not a RSAKey instance!");
61        return null;
62    }
63
64    @Override
65    public byte[] getDSAKeyParamQ(AndroidPrivateKey key) {
66        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
67        if (javaKey instanceof DSAKey) {
68            DSAParams params = ((DSAKey) javaKey).getParams();
69            return params.getQ().toByteArray();
70        }
71        Log.w(TAG, "Not a DSAKey instance!");
72        return null;
73    }
74
75    @Override
76    public byte[] getECKeyOrder(AndroidPrivateKey key) {
77        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
78        if (javaKey instanceof ECKey) {
79            ECParameterSpec params = ((ECKey) javaKey).getParams();
80            return params.getOrder().toByteArray();
81        }
82        Log.w(TAG, "Not an ECKey instance!");
83        return null;
84    }
85
86   @Override
87    public byte[] getPrivateKeyEncodedBytes(AndroidPrivateKey key) {
88        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
89        return javaKey.getEncoded();
90    }
91
92    @Override
93    public byte[] rawSignDigestWithPrivateKey(AndroidPrivateKey key,
94                                                     byte[] message) {
95        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
96        // Get the Signature for this key.
97        Signature signature = null;
98        // Hint: Algorithm names come from:
99        // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
100        try {
101            if (javaKey instanceof RSAPrivateKey) {
102                // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException
103                // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
104                // See https://android-review.googlesource.com/#/c/40352/
105                signature = Signature.getInstance("NONEwithRSA");
106            } else if (javaKey instanceof DSAPrivateKey) {
107                signature = Signature.getInstance("NONEwithDSA");
108            } else if (javaKey instanceof ECPrivateKey) {
109                signature = Signature.getInstance("NONEwithECDSA");
110            }
111        } catch (NoSuchAlgorithmException e) {
112            ;
113        }
114
115        if (signature == null) {
116            Log.e(TAG, "Unsupported private key algorithm: " + javaKey.getAlgorithm());
117            return null;
118        }
119
120        // Sign the message.
121        try {
122            signature.initSign(javaKey);
123            signature.update(message);
124            return signature.sign();
125        } catch (Exception e) {
126            Log.e(TAG, "Exception while signing message with " + javaKey.getAlgorithm() +
127                        " private key: " + e);
128            return null;
129        }
130    }
131
132    @Override
133    public int getPrivateKeyType(AndroidPrivateKey key) {
134        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
135        if (javaKey instanceof RSAPrivateKey)
136            return PrivateKeyType.RSA;
137        if (javaKey instanceof DSAPrivateKey)
138            return PrivateKeyType.DSA;
139        if (javaKey instanceof ECPrivateKey)
140            return PrivateKeyType.ECDSA;
141        else
142            return PrivateKeyType.INVALID;
143    }
144
145    @Override
146    public long getOpenSSLHandleForPrivateKey(AndroidPrivateKey key) {
147        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
148        // Sanity checks
149        if (javaKey == null) {
150            Log.e(TAG, "key == null");
151            return 0;
152        }
153        if (!(javaKey instanceof RSAPrivateKey)) {
154            Log.e(TAG, "does not implement RSAPrivateKey");
155            return 0;
156        }
157        // First, check that this is a proper instance of OpenSSLRSAPrivateKey
158        // or one of its sub-classes.
159        Class<?> superClass;
160        try {
161            superClass = Class.forName(
162                    "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
163        } catch (Exception e) {
164            // This may happen if the target device has a completely different
165            // implementation of the java.security APIs, compared to vanilla
166            // Android. Highly unlikely, but still possible.
167            Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
168            return 0;
169        }
170        if (!superClass.isInstance(javaKey)) {
171            // This may happen if the PrivateKey was not created by the "AndroidOpenSSL"
172            // provider, which should be the default. That could happen if an OEM decided
173            // to implement a different default provider. Also highly unlikely.
174            Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" +
175                       javaKey.getClass().getCanonicalName());
176            return 0;
177        }
178
179        try {
180            // Use reflection to invoke the 'getOpenSSLKey()' method on
181            // the private key. This returns another Java object that wraps
182            // a native EVP_PKEY. Note that the method is final, so calling
183            // the superclass implementation is ok.
184            Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
185            getKey.setAccessible(true);
186            Object opensslKey = null;
187            try {
188                opensslKey = getKey.invoke(javaKey);
189            } finally {
190                getKey.setAccessible(false);
191            }
192            if (opensslKey == null) {
193                // Bail when detecting OEM "enhancement".
194                Log.e(TAG, "getOpenSSLKey() returned null");
195                return 0;
196            }
197
198            // Use reflection to invoke the 'getPkeyContext' method on the
199            // result of the getOpenSSLKey(). This is an 32-bit integer
200            // which is the address of an EVP_PKEY object. Note that this
201            // method these days returns a 64-bit long, but since this code
202            // path is used for older Android versions, it may still return
203            // a 32-bit int here. To be on the safe side, we cast the return
204            // value via Number rather than directly to Integer or Long.
205            Method getPkeyContext;
206            try {
207                getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
208            } catch (Exception e) {
209                // Bail here too, something really not working as expected.
210                Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
211                return 0;
212            }
213            getPkeyContext.setAccessible(true);
214            long evp_pkey = 0;
215            try {
216                evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue();
217            } finally {
218                getPkeyContext.setAccessible(false);
219            }
220            if (evp_pkey == 0) {
221                // The PrivateKey is probably rotten for some reason.
222                Log.e(TAG, "getPkeyContext() returned null");
223            }
224            return evp_pkey;
225
226        } catch (Exception e) {
227            Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
228            return 0;
229        }
230    }
231
232    @Override
233    public void releaseKey(AndroidPrivateKey key) {
234        // no-op for in-process. GC will handle key collection
235    }
236}
237