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.
This commit is contained in:
Chung-yih Wang
2009-07-02 00:22:04 +08:00
parent d45dcbec85
commit eec11827a6
8 changed files with 828 additions and 140 deletions

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 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.
*/
package android.security;
import android.content.Context;
import android.content.Intent;
import android.security.Keystore;
import android.text.TextUtils;
/**
* The CertTool class provides the functions to list the certs/keys,
* generate the certificate request(csr) and store the certificate into
* keystore.
*
* {@hide}
*/
public class CertTool {
public static final String ACTION_ADD_CREDENTIAL =
"android.security.ADD_CREDENTIAL";
public static final String KEY_TYPE_NAME = "typeName";
public static final String KEY_ITEM = "item";
public static final String KEY_NAMESPACE = "namespace";
public static final String KEY_DESCRIPTION = "description";
private static final String TAG = "CertTool";
private static final String TITLE_CA_CERT = "CA Certificate";
private static final String TITLE_USER_CERT = "User Certificate";
private static final String TITLE_PKCS12_KEYSTORE = "PKCS12 Keystore";
private static final String TITLE_PRIVATE_KEY = "Private Key";
private static final String UNKNOWN = "Unknown";
private static final String ISSUER_NAME = "Issuer Name:";
private static final String DISTINCT_NAME = "Distinct Name:";
private static final String CA_CERTIFICATE = "CACERT";
private static final String USER_CERTIFICATE = "USRCERT";
private static final String USER_KEY = "USRKEY";
private static final String KEYNAME_DELIMITER = " ";
private static final Keystore keystore = Keystore.getInstance();
private native String generateCertificateRequest(int bits, String subject);
private native boolean isPkcs12Keystore(byte[] data);
private native int generateX509Certificate(byte[] data);
private native boolean isCaCertificate(int handle);
private native String getIssuerDN(int handle);
private native String getCertificateDN(int handle);
private native String getPrivateKeyPEM(int handle);
private native void freeX509Certificate(int handle);
public String getUserPrivateKey(String key) {
return USER_KEY + KEYNAME_DELIMITER + key;
}
public String getUserCertificate(String key) {
return USER_CERTIFICATE + KEYNAME_DELIMITER + key;
}
public String getCaCertificate(String key) {
return CA_CERTIFICATE + KEYNAME_DELIMITER + key;
}
public String[] getAllUserCertificateKeys() {
return keystore.listKeys(USER_KEY);
}
public String[] getAllCaCertificateKeys() {
return keystore.listKeys(CA_CERTIFICATE);
}
public String[] getSupportedKeyStrenghs() {
return new String[] {"High Grade", "Medium Grade"};
}
private int getKeyLength(int index) {
if (index == 0) return 2048;
return 1024;
}
public String generateKeyPair(int keyStrengthIndex, String challenge,
String dirName) {
return generateCertificateRequest(getKeyLength(keyStrengthIndex),
dirName);
}
private Intent prepareIntent(String title, byte[] data, String namespace,
String issuer, String distinctName) {
Intent intent = new Intent(ACTION_ADD_CREDENTIAL);
intent.putExtra(KEY_TYPE_NAME, title);
intent.putExtra(KEY_ITEM + "0", data);
intent.putExtra(KEY_NAMESPACE + "0", namespace);
intent.putExtra(KEY_DESCRIPTION + "0", ISSUER_NAME + issuer);
intent.putExtra(KEY_DESCRIPTION + "1", DISTINCT_NAME + distinctName);
return intent;
}
private void addExtraIntentInfo(Intent intent, String namespace,
String data) {
intent.putExtra(KEY_ITEM + "1", data);
intent.putExtra(KEY_NAMESPACE + "1", namespace);
}
public synchronized void addCertificate(byte[] data, Context context) {
int handle;
Intent intent = null;
if (isPkcs12Keystore(data)) {
intent = prepareIntent(TITLE_PKCS12_KEYSTORE, data, USER_KEY,
UNKNOWN, UNKNOWN);
} else if ((handle = generateX509Certificate(data)) != 0) {
String issuer = getIssuerDN(handle);
String distinctName = getCertificateDN(handle);
String privateKeyPEM = getPrivateKeyPEM(handle);
if (isCaCertificate(handle)) {
intent = prepareIntent(TITLE_CA_CERT, data, CA_CERTIFICATE,
issuer, distinctName);
} else {
intent = prepareIntent(TITLE_USER_CERT, data, USER_CERTIFICATE,
issuer, distinctName);
if (!TextUtils.isEmpty(privateKeyPEM)) {
addExtraIntentInfo(intent, USER_KEY, privateKeyPEM);
}
}
freeX509Certificate(handle);
}
if (intent != null) context.startActivity(intent);
}
}

View File

@@ -20,6 +20,7 @@ package android.security;
* The Keystore class provides the functions to list the certs/keys in keystore. * The Keystore class provides the functions to list the certs/keys in keystore.
* {@hide} * {@hide}
*/ */
public abstract class Keystore { public abstract class Keystore {
private static final String TAG = "Keystore"; private static final String TAG = "Keystore";
private static final String[] NOTFOUND = new String[0]; private static final String[] NOTFOUND = new String[0];
@@ -30,25 +31,18 @@ public abstract class Keystore {
return new FileKeystore(); return new FileKeystore();
} }
// for compatiblity, start from here public abstract int lock();
/** public abstract int unlock(String password);
*/ public abstract int getState();
public abstract String getUserkey(String key); public abstract int changePassword(String oldPassword, String newPassword);
public abstract int setPassword(String firstPassword);
/** public abstract String[] listKeys(String namespace);
*/ public abstract int put(String namespace, String keyname, String value);
public abstract String getCertificate(String key); public abstract String get(String namespace, String keyname);
public abstract int remove(String namespace, String keyname);
/** public abstract int reset();
*/
public abstract String[] getAllCertificateKeys();
/**
*/
public abstract String[] getAllUserkeyKeys();
// to here
// TODO: for migrating to the mini-keystore, clean up from here
/** /**
*/ */
public abstract String getCaCertificate(String key); public abstract String getCaCertificate(String key);
@@ -89,101 +83,41 @@ public abstract class Keystore {
int keyStrengthIndex, String challenge, String organizations); int keyStrengthIndex, String challenge, String organizations);
public abstract void addCertificate(byte[] cert); public abstract void addCertificate(byte[] cert);
// to here
private static class FileKeystore extends Keystore { private static class FileKeystore extends Keystore {
private static final String SERVICE_NAME = "keystore"; private static final String SERVICE_NAME = "keystore";
private static final String LIST_CA_CERTIFICATES = "listcacerts"; private static final String CA_CERTIFICATE = "CaCertificate";
private static final String LIST_USER_CERTIFICATES = "listusercerts"; private static final String USER_CERTIFICATE = "UserCertificate";
private static final String GET_CA_CERTIFICATE = "getcacert"; private static final String USER_KEY = "UserPrivateKey";
private static final String GET_USER_CERTIFICATE = "getusercert"; private static final String COMMAND_DELIMITER = " ";
private static final String GET_USER_KEY = "getuserkey";
private static final String ADD_CA_CERTIFICATE = "addcacert";
private static final String ADD_USER_CERTIFICATE = "addusercert";
private static final String ADD_USER_KEY = "adduserkey";
private static final String COMMAND_DELIMITER = "\t";
private static final ServiceCommand mServiceCommand = private static final ServiceCommand mServiceCommand =
new ServiceCommand(SERVICE_NAME); new ServiceCommand(SERVICE_NAME);
// for compatiblity, start from here // TODO: for migrating to the mini-keystore, start from here
private static final String LIST_CERTIFICATES = "listcerts";
private static final String LIST_USERKEYS = "listuserkeys";
private static final String PATH = "/data/misc/keystore/";
private static final String USERKEY_PATH = PATH + "userkeys/";
private static final String CERT_PATH = PATH + "certs/";
@Override
public String getUserkey(String key) {
return USERKEY_PATH + key;
}
@Override
public String getCertificate(String key) {
return CERT_PATH + key;
}
@Override
public String[] getAllCertificateKeys() {
try {
String result = mServiceCommand.execute(LIST_CERTIFICATES);
if (result != null) return result.split("\\s+");
return NOTFOUND;
} catch (NumberFormatException ex) {
return NOTFOUND;
}
}
@Override
public String[] getAllUserkeyKeys() {
try {
String result = mServiceCommand.execute(LIST_USERKEYS);
if (result != null) return result.split("\\s+");
return NOTFOUND;
} catch (NumberFormatException ex) {
return NOTFOUND;
}
}
// to here
@Override @Override
public String getUserPrivateKey(String key) { public String getUserPrivateKey(String key) {
return mServiceCommand.execute( return "";
GET_USER_KEY + COMMAND_DELIMITER + key);
} }
@Override @Override
public String getUserCertificate(String key) { public String getUserCertificate(String key) {
return mServiceCommand.execute( return "";
GET_USER_CERTIFICATE + COMMAND_DELIMITER + key);
} }
@Override @Override
public String getCaCertificate(String key) { public String getCaCertificate(String key) {
return mServiceCommand.execute( return "";
GET_CA_CERTIFICATE + COMMAND_DELIMITER + key);
} }
@Override @Override
public String[] getAllUserCertificateKeys() { public String[] getAllUserCertificateKeys() {
try { return new String[0];
String result = mServiceCommand.execute(LIST_USER_CERTIFICATES);
if (result != null) return result.split("\\s+");
return NOTFOUND;
} catch (NumberFormatException ex) {
return NOTFOUND;
}
} }
@Override @Override
public String[] getAllCaCertificateKeys() { public String[] getAllCaCertificateKeys() {
try { return new String[0];
String result = mServiceCommand.execute(LIST_CA_CERTIFICATES);
if (result != null) return result.split("\\s+");
return NOTFOUND;
} catch (NumberFormatException ex) {
return NOTFOUND;
}
} }
@Override @Override
@@ -221,25 +155,77 @@ public abstract class Keystore {
// TODO: real implementation // TODO: real implementation
} }
private boolean addUserCertificate(String key, String certificate, // to here
String privateKey) {
if(mServiceCommand.execute(ADD_USER_CERTIFICATE + COMMAND_DELIMITER @Override
+ key + COMMAND_DELIMITER + certificate) != null) { public int lock() {
if (mServiceCommand.execute(ADD_USER_KEY + COMMAND_DELIMITER Reply result = mServiceCommand.execute(ServiceCommand.LOCK, null);
+ key + COMMAND_DELIMITER + privateKey) != null) { return (result != null) ? result.returnCode : -1;
return true;
}
}
return false;
} }
private boolean addCaCertificate(String key, String content) { @Override
if (mServiceCommand.execute(ADD_CA_CERTIFICATE + COMMAND_DELIMITER public int unlock(String password) {
+ key + COMMAND_DELIMITER + content) != null) { Reply result = mServiceCommand.execute(ServiceCommand.UNLOCK,
return true; password);
} return (result != null) ? result.returnCode : -1;
return false;
} }
@Override
public int getState() {
Reply result = mServiceCommand.execute(ServiceCommand.GET_STATE,
null);
return (result != null) ? result.returnCode : -1;
}
@Override
public int changePassword(String oldPassword, String newPassword) {
Reply result = mServiceCommand.execute(ServiceCommand.PASSWD,
oldPassword + " " + newPassword);
return (result != null) ? result.returnCode : -1;
}
@Override
public int setPassword(String firstPassword) {
Reply result = mServiceCommand.execute(ServiceCommand.PASSWD,
firstPassword);
return (result != null) ? result.returnCode : -1;
}
@Override
public String[] listKeys(String namespace) {
Reply result = mServiceCommand.execute(ServiceCommand.LIST_KEYS,
namespace);
return (result != null) ? ((result.returnCode != 0) ? NOTFOUND :
new String(result.data, 0, result.len).split("\\s+"))
: NOTFOUND;
}
@Override
public int put(String namespace, String keyname, String value) {
Reply result = mServiceCommand.execute(ServiceCommand.PUT_KEY,
namespace + " " + keyname + " " + value);
return (result != null) ? result.returnCode : -1;
}
@Override
public String get(String namespace, String keyname) {
Reply result = mServiceCommand.execute(ServiceCommand.GET_KEY,
namespace + " " + keyname);
return (result != null) ? ((result.returnCode != 0) ? null :
new String(result.data, 0, result.len)) : null;
}
@Override
public int remove(String namespace, String keyname) {
Reply result = mServiceCommand.execute(ServiceCommand.REMOVE_KEY,
namespace + " " + keyname);
return (result != null) ? result.returnCode : -1;
}
@Override
public int reset() {
Reply result = mServiceCommand.execute(ServiceCommand.RESET, null);
return (result != null) ? result.returnCode : -1;
}
} }
} }

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 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.
*/
package android.security;
/*
* {@hide}
*/
public class Reply {
public int len;
public int returnCode;
public byte[] data = new byte[ServiceCommand.BUFFER_LENGTH];
}

View File

@@ -35,15 +35,25 @@ public class ServiceCommand {
public static final String SUCCESS = "0"; public static final String SUCCESS = "0";
public static final String FAILED = "-1"; public static final String FAILED = "-1";
// Opcodes for keystore commands.
public static final int LOCK = 0;
public static final int UNLOCK = 1;
public static final int PASSWD = 2;
public static final int GET_STATE = 3;
public static final int LIST_KEYS = 4;
public static final int GET_KEY = 5;
public static final int PUT_KEY = 6;
public static final int REMOVE_KEY = 7;
public static final int RESET = 8;
public static final int MAX_CMD_INDEX = 9;
public static final int BUFFER_LENGTH = 4096;
private String mServiceName; private String mServiceName;
private String mTag; private String mTag;
private InputStream mIn; private InputStream mIn;
private OutputStream mOut; private OutputStream mOut;
private LocalSocket mSocket; private LocalSocket mSocket;
private static final int BUFFER_LENGTH = 1024;
private byte buf[] = new byte[BUFFER_LENGTH];
private int buflen = 0;
private boolean connect() { private boolean connect() {
if (mSocket != null) { if (mSocket != null) {
@@ -104,35 +114,47 @@ public class ServiceCommand {
return false; return false;
} }
private boolean readReply() { private Reply readReply() {
int len, ret; byte buf[] = new byte[4];
buflen = 0; Reply reply = new Reply();
if (!readBytes(buf, 2)) return false; if (!readBytes(buf, 4)) return null;
ret = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); reply.len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8) |
if (ret != 0) return false; ((((int) buf[2]) & 0xff) << 16) |
((((int) buf[3]) & 0xff) << 24);
if (!readBytes(buf, 2)) return false; if (!readBytes(buf, 4)) return null;
len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); reply.returnCode = (((int) buf[0]) & 0xff) |
if (len > BUFFER_LENGTH) { ((((int) buf[1]) & 0xff) << 8) |
Log.e(mTag,"invalid reply length (" + len + ")"); ((((int) buf[2]) & 0xff) << 16) |
((((int) buf[3]) & 0xff) << 24);
if (reply.len > BUFFER_LENGTH) {
Log.e(mTag,"invalid reply length (" + reply.len + ")");
disconnect(); disconnect();
return false; return null;
} }
if (!readBytes(buf, len)) return false; if (!readBytes(reply.data, reply.len)) return null;
buflen = len; return reply;
return true;
} }
private boolean writeCommand(String _cmd) { private boolean writeCommand(int cmd, String _data) {
byte[] cmd = _cmd.getBytes(); byte buf[] = new byte[8];
int len = cmd.length; byte[] data = _data.getBytes();
if ((len < 1) || (len > BUFFER_LENGTH)) return false; int len = data.length;
// the length of data
buf[0] = (byte) (len & 0xff); buf[0] = (byte) (len & 0xff);
buf[1] = (byte) ((len >> 8) & 0xff); buf[1] = (byte) ((len >> 8) & 0xff);
buf[2] = (byte) ((len >> 16) & 0xff);
buf[3] = (byte) ((len >> 24) & 0xff);
// the opcode of the command
buf[4] = (byte) (cmd & 0xff);
buf[5] = (byte) ((cmd >> 8) & 0xff);
buf[6] = (byte) ((cmd >> 16) & 0xff);
buf[7] = (byte) ((cmd >> 24) & 0xff);
try { try {
mOut.write(buf, 0, 2); mOut.write(buf, 0, 8);
mOut.write(cmd, 0, len); mOut.write(data, 0, len);
} catch (IOException ex) { } catch (IOException ex) {
Log.e(mTag,"write error"); Log.e(mTag,"write error");
disconnect(); disconnect();
@@ -141,32 +163,28 @@ public class ServiceCommand {
return true; return true;
} }
private String executeCommand(String cmd) { private Reply executeCommand(int cmd, String data) {
if (!writeCommand(cmd)) { if (!writeCommand(cmd, data)) {
/* If service died and restarted in the background /* If service died and restarted in the background
* (unlikely but possible) we'll fail on the next * (unlikely but possible) we'll fail on the next
* write (this one). Try to reconnect and write * write (this one). Try to reconnect and write
* the command one more time before giving up. * the command one more time before giving up.
*/ */
Log.e(mTag, "write command failed? reconnect!"); Log.e(mTag, "write command failed? reconnect!");
if (!connect() || !writeCommand(cmd)) { if (!connect() || !writeCommand(cmd, data)) {
return null; return null;
} }
} }
if (readReply()) { return readReply();
return new String(buf, 0, buflen);
} else {
return null;
}
} }
public synchronized String execute(String cmd) { public synchronized Reply execute(int cmd, String data) {
String result; Reply result;
if (!connect()) { if (!connect()) {
Log.e(mTag, "connection failed"); Log.e(mTag, "connection failed");
return null; return null;
} }
result = executeCommand(cmd); result = executeCommand(cmd, data);
disconnect(); disconnect();
return result; return result;
} }

31
keystore/jni/Android.mk Normal file
View File

@@ -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)

249
keystore/jni/cert.c Normal file
View File

@@ -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;
}

59
keystore/jni/cert.h Normal file
View File

@@ -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

176
keystore/jni/certtool.c Normal file
View File

@@ -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;
}