Add CertTool for handling the keygen and certificate download.
1. Have the new Keystore for mini-keystore impelemntation.
2. Add CertTool library and jni dll for handling keygen and certificates.
3. Make Reply hidden.
4. Revert some 'incorrect' change and correct the description.
diff --git a/keystore/jni/Android.mk b/keystore/jni/Android.mk
new file mode 100644
index 0000000..92c2d6d
--- /dev/null
+++ b/keystore/jni/Android.mk
@@ -0,0 +1,31 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ cert.c certtool.c
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE) \
+ external/openssl/include
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libnativehelper \
+ libutils \
+ libcrypto
+
+ifeq ($(TARGET_SIMULATOR),true)
+ifeq ($(TARGET_OS),linux)
+ifeq ($(TARGET_ARCH),x86)
+LOCAL_LDLIBS += -lpthread -ldl -lrt -lssl
+endif
+endif
+endif
+
+ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
+ LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
+endif
+
+LOCAL_MODULE:= libcerttool_jni
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/keystore/jni/cert.c b/keystore/jni/cert.c
new file mode 100644
index 0000000..07f0e86
--- /dev/null
+++ b/keystore/jni/cert.c
@@ -0,0 +1,249 @@
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "CertTool"
+
+#include <stdio.h>
+#include <openssl/engine.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rsa.h>
+#include <openssl/x509v3.h>
+#include <cutils/log.h>
+
+#include "cert.h"
+
+static PKEY_STORE pkey_store[KEYGEN_STORE_SIZE];
+static int store_index = 0;
+
+static char emsg[][30] = {
+ "",
+ STR(ERR_INVALID_KEY_LENGTH),
+ STR(ERR_CONSTRUCT_NEW_DATA),
+ STR(ERR_RSA_KEYGEN),
+ STR(ERR_X509_PROCESS),
+ STR(ERR_BIO_READ),
+};
+
+static void save_in_store(X509_REQ *req, EVP_PKEY *pkey)
+{
+ EVP_PKEY *newpkey = EVP_PKEY_new();
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey);
+ EVP_PKEY_set1_RSA(newpkey, rsa);
+ PKEY_STORE_free(pkey_store[store_index]);
+ pkey_store[store_index].key_len =
+ i2d_X509_PUBKEY(req->req_info->pubkey, &pkey_store[store_index].public_key);
+ pkey_store[store_index++].pkey = newpkey;
+ store_index %= KEYGEN_STORE_SIZE;
+ RSA_free(rsa);
+}
+
+static EVP_PKEY *get_pkey_from_store(X509 *cert)
+{
+ int i, key_len;
+ unsigned char *buf = NULL;
+ if ((key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &buf)) == 0) {
+ return NULL;
+ }
+ for (i = 0 ; i < KEYGEN_STORE_SIZE ; ++i) {
+ if ((key_len == pkey_store[i].key_len) &&
+ memcmp(buf, pkey_store[i].public_key, key_len) == 0) {
+ break;
+ }
+ }
+ free(buf);
+ return (i == KEYGEN_STORE_SIZE) ? NULL : pkey_store[i].pkey;
+}
+
+int gen_csr(int bits, const char *organizations, char reply[REPLY_MAX])
+{
+ int len, ret_code = 0;
+ BIGNUM *bn = NULL;
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+ X509_REQ *req = NULL;
+ X509_NAME *name = NULL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) goto err;
+
+ if ((bits != KEYLENGTH_MEDIUM) && (bits != KEYLENGTH_MAXIMUM)) {
+ ret_code = ERR_INVALID_KEY_LENGTH;
+ goto err;
+ }
+
+ if (((pkey = EVP_PKEY_new()) == NULL) ||
+ ((req = X509_REQ_new()) == NULL) ||
+ ((rsa = RSA_new()) == NULL) || ((bn = BN_new()) == NULL)) {
+ ret_code = ERR_CONSTRUCT_NEW_DATA;
+ goto err;
+ }
+
+ if (!BN_set_word(bn, RSA_F4) ||
+ !RSA_generate_key_ex(rsa, bits, bn, NULL) ||
+ !EVP_PKEY_assign_RSA(pkey, rsa)) {
+ ret_code = ERR_RSA_KEYGEN;
+ goto err;
+ }
+
+ // rsa will be part of the req, it will be freed in X509_REQ_free(req)
+ rsa = NULL;
+
+ X509_REQ_set_pubkey(req, pkey);
+ name = X509_REQ_get_subject_name(req);
+
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
+ (const unsigned char *)"US", -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+ (const unsigned char *) ANDROID_KEYSTORE,
+ -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
+ (const unsigned char *)organizations, -1, -1, 0);
+
+ if (!X509_REQ_sign(req, pkey, EVP_md5()) ||
+ (PEM_write_bio_X509_REQ(bio, req) <= 0)) {
+ ret_code = ERR_X509_PROCESS;
+ goto err;
+ }
+ if ((len = BIO_read(bio, reply, REPLY_MAX - 1)) > 0) {
+ reply[len] = 0;
+ save_in_store(req, pkey);
+ } else {
+ ret_code = ERR_BIO_READ;
+ }
+
+err:
+ if (rsa) RSA_free(rsa);
+ if (bn) BN_free(bn);
+ if (req) X509_REQ_free(req);
+ if (pkey) EVP_PKEY_free(pkey);
+ if (bio) BIO_free(bio);
+ if ((ret_code > 0) && (ret_code < ERR_MAXIMUM)) LOGE(emsg[ret_code]);
+ return ret_code;
+}
+
+int is_pkcs12(const char *buf, int bufLen)
+{
+ int ret = 0;
+ BIO *bp = NULL;
+ PKCS12 *p12 = NULL;
+
+ if (!buf || bufLen < 1) goto err;
+
+ if (buf[0] != 48) goto err; // it is not DER.
+
+ if (!BIO_write(bp, buf, bufLen)) goto err;
+
+ if ((p12 = d2i_PKCS12_bio(bp, NULL)) != NULL) {
+ PKCS12_free(p12);
+ ret = 1;
+ }
+err:
+ if (bp) BIO_free(bp);
+ return ret;
+}
+
+X509* parse_cert(const char *buf, int bufLen)
+{
+ X509 *cert = NULL;
+ BIO *bp = NULL;
+
+ if(!buf || bufLen < 1)
+ return NULL;
+
+ bp = BIO_new(BIO_s_mem());
+ if (!bp) goto err;
+
+ if (!BIO_write(bp, buf, bufLen)) goto err;
+
+ cert = PEM_read_bio_X509(bp, NULL, NULL, NULL);
+ if (!cert) {
+ BIO_free(bp);
+ if((bp = BIO_new(BIO_s_mem())) == NULL) goto err;
+
+ if(!BIO_write(bp, (char *) buf, bufLen)) goto err;
+ cert = d2i_X509_bio(bp, NULL);
+ }
+
+err:
+ if (bp) BIO_free(bp);
+ return cert;
+}
+
+static int get_distinct_name(X509_NAME *dname, char *buf, int size)
+{
+ int i, len;
+ char *p, *name;
+
+ if (X509_NAME_oneline(dname, buf, size) == NULL) {
+ return -1;
+ }
+ name = strstr(buf, "/CN=");
+ p = name = name ? (name + 4) : buf;
+ while (*p != 0) {
+ if (*p == ' ') *p = '_';
+ if (*p == '/') {
+ *p = 0;
+ break;
+ }
+ ++p;
+ }
+ return 0;
+}
+
+int get_cert_name(X509 *cert, char *buf, int size)
+{
+ if (!cert) return -1;
+ return get_distinct_name(X509_get_subject_name(cert), buf, size);
+}
+
+int get_issuer_name(X509 *cert, char *buf, int size)
+{
+ if (!cert) return -1;
+ return get_distinct_name(X509_get_issuer_name(cert), buf, size);
+}
+
+int is_ca_cert(X509 *cert)
+{
+ int ret = 0;
+ BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)
+ X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL);
+ if (bs != NULL) ret = bs->ca;
+ if (bs) BASIC_CONSTRAINTS_free(bs);
+ return ret;
+}
+
+int get_private_key_pem(X509 *cert, char *buf, int size)
+{
+ int len = 0;
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = get_pkey_from_store(cert);
+
+ if (pkey == NULL) return -1;
+
+ bio = BIO_new(BIO_s_mem());
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) goto err;
+ if (!PEM_write_bio_PrivateKey(bio, pkey, NULL,NULL,0,NULL, NULL)) {
+ goto err;
+ }
+ if ((len = BIO_read(bio, buf, size - 1)) > 0) {
+ buf[len] = 0;
+ }
+err:
+ if (bio) BIO_free(bio);
+ return (len == 0) ? -1 : 0;
+}
diff --git a/keystore/jni/cert.h b/keystore/jni/cert.h
new file mode 100644
index 0000000..a9807b1
--- /dev/null
+++ b/keystore/jni/cert.h
@@ -0,0 +1,59 @@
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef __CERT_H__
+#define __CERT_H__
+
+#define ANDROID_KEYSTORE "Android Keystore"
+#define KEYGEN_STORE_SIZE 5
+#define KEYLENGTH_MEDIUM 1024
+#define KEYLENGTH_MAXIMUM 2048
+#define MAX_CERT_NAME_LEN 128
+#define MAX_PEM_LENGTH 4096
+#define REPLY_MAX MAX_PEM_LENGTH
+
+
+#define STR(token) #token
+#define ERR_INVALID_KEY_LENGTH 1
+#define ERR_CONSTRUCT_NEW_DATA 2
+#define ERR_RSA_KEYGEN 3
+#define ERR_X509_PROCESS 4
+#define ERR_BIO_READ 5
+#define ERR_MAXIMUM 6
+
+typedef struct {
+ EVP_PKEY *pkey;
+ unsigned char *public_key;
+ int key_len;
+} PKEY_STORE;
+
+#define PKEY_STORE_free(x) { \
+ if(x.pkey) EVP_PKEY_free(x.pkey); \
+ if(x.public_key) free(x.public_key); \
+}
+
+#define nelem(x) (sizeof (x) / sizeof *(x))
+
+int gen_csr(int bits, const char *organizations, char reply[REPLY_MAX]);
+int is_pkcs12(const char *buf, int bufLen);
+X509* parse_cert(const char *buf, int bufLen);
+int get_cert_name(X509 *cert, char *buf, int size);
+int get_issuer_name(X509 *cert, char *buf, int size);
+int is_ca_cert(X509 *cert);
+int get_private_key_pem(X509 *cert, char *buf, int size);
+
+#endif
diff --git a/keystore/jni/certtool.c b/keystore/jni/certtool.c
new file mode 100644
index 0000000..c2a137e
--- /dev/null
+++ b/keystore/jni/certtool.c
@@ -0,0 +1,176 @@
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+#define LOG_TAG "CertTool"
+
+#include <string.h>
+#include <jni.h>
+#include <cutils/log.h>
+#include <openssl/x509v3.h>
+
+#include "cert.h"
+
+jstring
+android_security_CertTool_generateCertificateRequest(JNIEnv* env,
+ jobject thiz,
+ jint bits,
+ jstring subject)
+
+{
+ char csr[REPLY_MAX];
+ if (gen_csr(bits, subject, csr) == 0) {
+ return (*env)->NewStringUTF(env, csr);
+ }
+ return NULL;
+}
+
+jboolean
+android_security_CertTool_isPkcs12Keystore(JNIEnv* env,
+ jobject thiz,
+ jbyteArray data)
+{
+ char buf[REPLY_MAX];
+ int len = (*env)->GetArrayLength(env, data);
+
+ if (len > REPLY_MAX) return 0;
+ (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf);
+ return (jboolean) is_pkcs12(buf, len);
+}
+
+jint
+android_security_CertTool_generateX509Certificate(JNIEnv* env,
+ jobject thiz,
+ jbyteArray data)
+{
+ char buf[REPLY_MAX];
+ int len = (*env)->GetArrayLength(env, data);
+
+ if (len > REPLY_MAX) return 0;
+ (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf);
+ return (jint) parse_cert(buf, len);
+}
+
+jboolean android_security_CertTool_isCaCertificate(JNIEnv* env,
+ jobject thiz,
+ jint handle)
+{
+ return (handle == 0) ? (jboolean)0 : (jboolean) is_ca_cert((X509*)handle);
+}
+
+jstring android_security_CertTool_getIssuerDN(JNIEnv* env,
+ jobject thiz,
+ jint handle)
+{
+ char issuer[MAX_CERT_NAME_LEN];
+
+ if (handle == 0) return NULL;
+ if (get_issuer_name((X509*)handle, issuer, MAX_CERT_NAME_LEN)) return NULL;
+ return (*env)->NewStringUTF(env, issuer);
+}
+
+jstring android_security_CertTool_getCertificateDN(JNIEnv* env,
+ jobject thiz,
+ jint handle)
+{
+ char name[MAX_CERT_NAME_LEN];
+ if (handle == 0) return NULL;
+ if (get_cert_name((X509*)handle, name, MAX_CERT_NAME_LEN)) return NULL;
+ return (*env)->NewStringUTF(env, name);
+}
+
+jstring android_security_CertTool_getPrivateKeyPEM(JNIEnv* env,
+ jobject thiz,
+ jint handle)
+{
+ char pem[MAX_PEM_LENGTH];
+ if (handle == 0) return NULL;
+ if (get_private_key_pem((X509*)handle, pem, MAX_PEM_LENGTH)) return NULL;
+ return (*env)->NewStringUTF(env, pem);
+}
+
+void android_security_CertTool_freeX509Certificate(JNIEnv* env,
+ jobject thiz,
+ jint handle)
+{
+ if (handle != 0) X509_free((X509*)handle);
+}
+
+/*
+ * Table of methods associated with the CertTool class.
+ */
+static JNINativeMethod gCertToolMethods[] = {
+ /* name, signature, funcPtr */
+ {"generateCertificateRequest", "(ILjava/lang/String;)Ljava/lang/String;",
+ (void*)android_security_CertTool_generateCertificateRequest},
+ {"isPkcs12Keystore", "(B[)I",
+ (void*)android_security_CertTool_isPkcs12Keystore},
+ {"generateX509Certificate", "(B[)I",
+ (void*)android_security_CertTool_generateX509Certificate},
+ {"isCaCertificate", "(I)Z",
+ (void*)android_security_CertTool_isCaCertificate},
+ {"getIssuerDN", "(I)Ljava/lang/String;",
+ (void*)android_security_CertTool_getIssuerDN},
+ {"getCertificateDN", "(I)Ljava/lang/String;",
+ (void*)android_security_CertTool_getCertificateDN},
+ {"getPrivateKeyPEM", "(I)Ljava/lang/String;",
+ (void*)android_security_CertTool_getPrivateKeyPEM},
+ {"freeX509Certificate", "(I)V",
+ (void*)android_security_CertTool_freeX509Certificate},
+};
+
+/*
+ * Register several native methods for one class.
+ */
+static int registerNatives(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods)
+{
+ jclass clazz;
+
+ clazz = (*env)->FindClass(env, className);
+ if (clazz == NULL) {
+ LOGE("Can not find class %s\n", className);
+ return JNI_FALSE;
+ }
+
+ if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
+ LOGE("Can not RegisterNatives\n");
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+
+ if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ goto bail;
+ }
+
+ if (!registerNatives(env, "android/security/CertTool",
+ gCertToolMethods, nelem(gCertToolMethods))) {
+ goto bail;
+ }
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}