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    private Object getOpenSSLKeyForPrivateKey(AndroidPrivateKey key) {
146        PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey();
147        // Sanity checks
148        if (javaKey == null) {
149            Log.e(TAG, "key == null");
150            return null;
151        }
152        if (!(javaKey instanceof RSAPrivateKey)) {
153            Log.e(TAG, "does not implement RSAPrivateKey");
154            return null;
155        }
156        // First, check that this is a proper instance of OpenSSLRSAPrivateKey
157        // or one of its sub-classes.
158        Class<?> superClass;
159        try {
160            superClass = Class.forName(
161                    "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
162        } catch (Exception e) {
163            // This may happen if the target device has a completely different
164            // implementation of the java.security APIs, compared to vanilla
165            // Android. Highly unlikely, but still possible.
166            Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
167            return null;
168        }
169        if (!superClass.isInstance(javaKey)) {
170            // This may happen if the PrivateKey was not created by the "AndroidOpenSSL"
171            // provider, which should be the default. That could happen if an OEM decided
172            // to implement a different default provider. Also highly unlikely.
173            Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" +
174                       javaKey.getClass().getCanonicalName());
175            return null;
176        }
177
178        try {
179            // Use reflection to invoke the 'getOpenSSLKey()' method on the
180            // private key. This returns another Java object that wraps a native
181            // EVP_PKEY and OpenSSLEngine. Note that the method is final in Android
182            // 4.1, so calling the superclass implementation is ok.
183            Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
184            getKey.setAccessible(true);
185            Object opensslKey = null;
186            try {
187                opensslKey = getKey.invoke(javaKey);
188            } finally {
189                getKey.setAccessible(false);
190            }
191            if (opensslKey == null) {
192                // Bail when detecting OEM "enhancement".
193                Log.e(TAG, "getOpenSSLKey() returned null");
194                return null;
195            }
196            return opensslKey;
197        } catch (Exception e) {
198            Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
199            return null;
200        }
201    }
202
203    @Override
204    public long getOpenSSLHandleForPrivateKey(AndroidPrivateKey key) {
205        Object opensslKey = getOpenSSLKeyForPrivateKey(key);
206        if (opensslKey == null)
207            return 0;
208
209        try {
210            // Use reflection to invoke the 'getPkeyContext' method on the
211            // result of the getOpenSSLKey(). This is an 32-bit integer
212            // which is the address of an EVP_PKEY object. Note that this
213            // method these days returns a 64-bit long, but since this code
214            // path is used for older Android versions, it may still return
215            // a 32-bit int here. To be on the safe side, we cast the return
216            // value via Number rather than directly to Integer or Long.
217            Method getPkeyContext;
218            try {
219                getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
220            } catch (Exception e) {
221                // Bail here too, something really not working as expected.
222                Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
223                return 0;
224            }
225            getPkeyContext.setAccessible(true);
226            long evp_pkey = 0;
227            try {
228                evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue();
229            } finally {
230                getPkeyContext.setAccessible(false);
231            }
232            if (evp_pkey == 0) {
233                // The PrivateKey is probably rotten for some reason.
234                Log.e(TAG, "getPkeyContext() returned null");
235            }
236            return evp_pkey;
237
238        } catch (Exception e) {
239            Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
240            return 0;
241        }
242    }
243
244    @Override
245    public Object getOpenSSLEngineForPrivateKey(AndroidPrivateKey key) {
246        // Find the system OpenSSLEngine class.
247        Class<?> engineClass;
248        try {
249            engineClass = Class.forName(
250                    "org.apache.harmony.xnet.provider.jsse.OpenSSLEngine");
251        } catch (Exception e) {
252            // This may happen if the target device has a completely different
253            // implementation of the java.security APIs, compared to vanilla
254            // Android. Highly unlikely, but still possible.
255            Log.e(TAG, "Cannot find system OpenSSLEngine class: " + e);
256            return null;
257        }
258
259        Object opensslKey = getOpenSSLKeyForPrivateKey(key);
260        if (opensslKey == null)
261            return null;
262
263        try {
264            // Use reflection to invoke the 'getEngine' method on the
265            // result of the getOpenSSLKey().
266            Method getEngine;
267            try {
268                getEngine = opensslKey.getClass().getDeclaredMethod("getEngine");
269            } catch (Exception e) {
270                // Bail here too, something really not working as expected.
271                Log.e(TAG, "No getEngine() method on OpenSSLKey member:" + e);
272                return null;
273            }
274            getEngine.setAccessible(true);
275            Object engine = null;
276            try {
277                engine = getEngine.invoke(opensslKey);
278            } finally {
279                getEngine.setAccessible(false);
280            }
281            if (engine == null) {
282                // The PrivateKey is probably rotten for some reason.
283                Log.e(TAG, "getEngine() returned null");
284            }
285            // Sanity-check the returned engine.
286            if (!engineClass.isInstance(engine)) {
287                Log.e(TAG, "Engine is not an OpenSSLEngine instance, its class name is:" +
288                        engine.getClass().getCanonicalName());
289                return null;
290            }
291            return engine;
292
293        } catch (Exception e) {
294            Log.e(TAG, "Exception while trying to retrieve OpenSSLEngine object: " + e);
295            return null;
296        }
297    }
298
299    @Override
300    public void releaseKey(AndroidPrivateKey key) {
301        // no-op for in-process. GC will handle key collection
302    }
303}
304