android: Leak the private key reference on Jelly Bean to avoid a bug in the framework
[strongswan.git] / src / frontends / android / jni / libandroidbridge / backend / android_private_key.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "android_private_key.h"
17
18 #include "../android_jni.h"
19 #include <debug.h>
20
21 typedef struct private_private_key_t private_private_key_t;
22
23 /**
24 * Private data of a android_private_key_t object.
25 */
26 struct private_private_key_t {
27
28 /**
29 * Public interface
30 */
31 private_key_t public;
32
33 /**
34 * reference to the Java PrivateKey object
35 */
36 jobject key;
37
38 /**
39 * Java class used to build signatures
40 */
41 jclass signature_class;
42
43 /**
44 * public key that belongs to this private key
45 */
46 public_key_t *pubkey;
47
48 /**
49 * reference count
50 */
51 refcount_t ref;
52 };
53
54 /**
55 * Converts the given Java byte array to a chunk
56 */
57 static chunk_t chunk_from_byte_array(JNIEnv *env, jbyteArray jbytearray)
58 {
59 chunk_t chunk;
60
61 chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray));
62 (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr);
63 return chunk;
64 }
65
66 /**
67 * Converts the given chunk to a Java byte array
68 */
69 static jbyteArray byte_array_from_chunk(JNIEnv *env, chunk_t chunk)
70 {
71 jbyteArray jbytearray;
72
73 jbytearray = (*env)->NewByteArray(env, chunk.len);
74 (*env)->SetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr);
75 return jbytearray;
76 }
77
78 METHOD(private_key_t, sign, bool,
79 private_private_key_t *this, signature_scheme_t scheme,
80 chunk_t data, chunk_t *signature)
81 {
82 JNIEnv *env;
83 jmethodID method_id;
84 const char *method;
85 jstring jmethod;
86 jobject jsignature;
87 jbyteArray jdata, jsigarray;
88
89 switch (scheme)
90 {
91 case SIGN_RSA_EMSA_PKCS1_MD5:
92 method = "MD5withRSA";
93 break;
94 case SIGN_RSA_EMSA_PKCS1_SHA1:
95 method = "SHA1withRSA";
96 break;
97 case SIGN_RSA_EMSA_PKCS1_SHA224:
98 method = "SHA224withRSA";
99 break;
100 case SIGN_RSA_EMSA_PKCS1_SHA256:
101 method = "SHA256withRSA";
102 break;
103 case SIGN_RSA_EMSA_PKCS1_SHA384:
104 method = "SHA384withRSA";
105 break;
106 case SIGN_RSA_EMSA_PKCS1_SHA512:
107 method = "SHA512withRSA";
108 break;
109 default:
110 DBG1(DBG_LIB, "signature scheme %N not supported via JNI",
111 signature_scheme_names, scheme);
112 return FALSE;
113 }
114
115 androidjni_attach_thread(&env);
116 /* we use java.security.Signature to create the signature without requiring
117 * access to the actual private key */
118 method_id = (*env)->GetStaticMethodID(env, this->signature_class,
119 "getInstance", "(Ljava/lang/String;)Ljava/security/Signature;");
120 if (!method_id)
121 {
122 goto failed;
123 }
124 jmethod = (*env)->NewStringUTF(env, method);
125 if (!jmethod)
126 {
127 goto failed;
128 }
129 jsignature = (*env)->CallStaticObjectMethod(env, this->signature_class,
130 method_id, jmethod);
131 if (!jsignature)
132 {
133 goto failed;
134 }
135 method_id = (*env)->GetMethodID(env, this->signature_class, "initSign",
136 "(Ljava/security/PrivateKey;)V");
137 if (!method_id)
138 {
139 goto failed;
140 }
141 (*env)->CallVoidMethod(env, jsignature, method_id, this->key);
142 if (androidjni_exception_occurred(env))
143 {
144 goto failed;
145 }
146 method_id = (*env)->GetMethodID(env, this->signature_class, "update",
147 "([B)V");
148 if (!method_id)
149 {
150 goto failed;
151 }
152 jdata = byte_array_from_chunk(env, data);
153 (*env)->CallVoidMethod(env, jsignature, method_id, jdata);
154 if (androidjni_exception_occurred(env))
155 {
156 goto failed;
157 }
158 method_id = (*env)->GetMethodID(env, this->signature_class, "sign",
159 "()[B");
160 if (!method_id)
161 {
162 goto failed;
163 }
164 jsigarray = (*env)->CallObjectMethod(env, jsignature, method_id);
165 if (!jsigarray)
166 {
167 goto failed;
168 }
169 *signature = chunk_from_byte_array(env, jsigarray);
170 androidjni_detach_thread();
171 return TRUE;
172
173 failed:
174 DBG1(DBG_LIB, "failed to build %N signature via JNI",
175 signature_scheme_names, scheme);
176 androidjni_exception_occurred(env);
177 androidjni_detach_thread();
178 return FALSE;
179 }
180
181 METHOD(private_key_t, get_type, key_type_t,
182 private_private_key_t *this)
183 {
184 return KEY_RSA;
185 }
186
187 METHOD(private_key_t, decrypt, bool,
188 private_private_key_t *this, encryption_scheme_t scheme,
189 chunk_t crypto, chunk_t *plain)
190 {
191 DBG1(DBG_LIB, "private key decryption is currently not supported via JNI");
192 return FALSE;
193 }
194
195 METHOD(private_key_t, get_keysize, int,
196 private_private_key_t *this)
197 {
198 return this->pubkey->get_keysize(this->pubkey);
199 }
200
201 METHOD(private_key_t, get_public_key, public_key_t*,
202 private_private_key_t *this)
203 {
204 return this->pubkey->get_ref(this->pubkey);
205 }
206
207 METHOD(private_key_t, get_encoding, bool,
208 private_private_key_t *this, cred_encoding_type_t type,
209 chunk_t *encoding)
210 {
211 return FALSE;
212 }
213
214 METHOD(private_key_t, get_fingerprint, bool,
215 private_private_key_t *this, cred_encoding_type_t type, chunk_t *fp)
216 {
217 return this->pubkey->get_fingerprint(this->pubkey, type, fp);
218 }
219
220 METHOD(private_key_t, get_ref, private_key_t*,
221 private_private_key_t *this)
222 {
223 ref_get(&this->ref);
224 return &this->public;
225 }
226
227 METHOD(private_key_t, destroy, void,
228 private_private_key_t *this)
229 {
230 if (ref_put(&this->ref))
231 {
232 JNIEnv *env;
233
234 androidjni_attach_thread(&env);
235 if (android_sdk_version >= ANDROID_JELLY_BEAN)
236 { /* there is a bug in JB that causes a SIGSEGV if the key object is
237 * garbage collected so we intentionally leak the reference to it */
238 DBG1(DBG_LIB, "intentionally leaking private key reference due to "
239 "a bug in the framework");
240 }
241 else
242 {
243 (*env)->DeleteGlobalRef(env, this->key);
244 }
245 (*env)->DeleteGlobalRef(env, this->signature_class);
246 androidjni_detach_thread();
247 this->pubkey->destroy(this->pubkey);
248 free(this);
249 }
250 }
251
252 /*
253 * See header
254 */
255 private_key_t *android_private_key_create(jobject key, public_key_t *pubkey)
256 {
257 JNIEnv *env;
258 private_private_key_t *this;
259
260 INIT(this,
261 .public = {
262 .get_type = _get_type,
263 .sign = _sign,
264 .decrypt = _decrypt,
265 .get_keysize = _get_keysize,
266 .get_public_key = _get_public_key,
267 .belongs_to = private_key_belongs_to,
268 .equals = private_key_equals,
269 .get_fingerprint = _get_fingerprint,
270 .has_fingerprint = private_key_has_fingerprint,
271 .get_encoding = _get_encoding,
272 .get_ref = _get_ref,
273 .destroy = _destroy,
274 },
275 .ref = 1,
276 .pubkey = pubkey,
277 );
278
279 if (!pubkey)
280 {
281 free(this);
282 return NULL;
283 }
284
285 /* in ICS we could simply call getEncoded and use the PKCS#8/DER encoded
286 * private key, since JB that's not possible as there is no direct access
287 * to private keys anymore (as these could now be hardware backed) */
288 androidjni_attach_thread(&env);
289 this->key = (*env)->NewGlobalRef(env, key);
290 this->signature_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env,
291 "java/security/Signature"));
292 androidjni_detach_thread();
293 return &this->public;
294 }