Merge change 5615 into donut
* changes: Implement the generic mini-keystore for security.
This commit is contained in:
@@ -4,13 +4,14 @@ LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
keystore.c commands.c
|
||||
netkeystore.c keymgmt.c
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(call include-path-for, system-core)/cutils
|
||||
$(call include-path-for, system-core)/cutils \
|
||||
external/openssl/include
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcutils
|
||||
libcutils libssl
|
||||
|
||||
LOCAL_STATIC_LIBRARIES :=
|
||||
|
||||
|
||||
83
cmds/keystore/certtool.h
Normal file
83
cmds/keystore/certtool.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
**
|
||||
** 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 __CERTTOOL_H__
|
||||
#define __CERTTOOL_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/log.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "netkeystore.h"
|
||||
|
||||
/*
|
||||
* The specific function 'get_cert' is used in daemons to get the key value
|
||||
* from keystore. Caller should allocate the buffer and the length of the buffer
|
||||
* should be MAX_KEY_VALUE_LENGTH.
|
||||
*/
|
||||
static inline int get_cert(char *certname, unsigned char *value, int *size)
|
||||
{
|
||||
int count, fd, ret = -1;
|
||||
LPC_MARSHAL cmd;
|
||||
char delimiter[] = "_";
|
||||
char *namespace, *keyname;
|
||||
char *context = NULL;
|
||||
|
||||
if (value == NULL) {
|
||||
LOGE("get_cert: value is null\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = socket_local_client(SOCKET_PATH,
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED,
|
||||
SOCK_STREAM);
|
||||
if (fd == -1) {
|
||||
LOGE("Keystore service is not up and running.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd.opcode = GET;
|
||||
if (((namespace = strtok_r(certname, delimiter, &context)) == NULL) ||
|
||||
((keyname = strtok_r(NULL, delimiter, &context)) == NULL)) {
|
||||
goto err;
|
||||
}
|
||||
if ((cmd.len = snprintf((char*)cmd.data, BUFFER_MAX, "%s %s", namespace, keyname))
|
||||
> (2 * MAX_KEY_NAME_LENGTH + 1)) goto err;
|
||||
|
||||
if (write_marshal(fd, &cmd)) {
|
||||
LOGE("Incorrect command or command line is too long.\n");
|
||||
goto err;
|
||||
}
|
||||
if (read_marshal(fd, &cmd)) {
|
||||
LOGE("Failed to read the result.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
// copy the result if succeeded.
|
||||
if (!cmd.retcode && cmd.len <= BUFFER_MAX) {
|
||||
memcpy(value, cmd.data, cmd.len);
|
||||
ret = 0;
|
||||
*size = cmd.len;
|
||||
}
|
||||
err:
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
|
||||
#include "keystore.h"
|
||||
|
||||
static DIR *open_keystore(const char *dir)
|
||||
{
|
||||
DIR *d;
|
||||
if ((d = opendir(dir)) == NULL) {
|
||||
if (mkdir(dir, 0770) < 0) {
|
||||
LOGE("cannot create dir '%s': %s\n", dir, strerror(errno));
|
||||
unlink(dir);
|
||||
return NULL;
|
||||
}
|
||||
d = open_keystore(dir);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static int list_files(const char *dir, char reply[REPLY_MAX])
|
||||
{
|
||||
struct dirent *de;
|
||||
DIR *d;
|
||||
|
||||
if ((d = open_keystore(dir)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
reply[0]=0;
|
||||
while ((de = readdir(d))) {
|
||||
if (de->d_type != DT_DIR) continue;
|
||||
if ((strcmp(DOT, de->d_name) == 0) ||
|
||||
(strcmp(DOTDOT, de->d_name) == 0)) continue;
|
||||
if (reply[0] != 0) strlcat(reply, " ", REPLY_MAX);
|
||||
if (strlcat(reply, de->d_name, REPLY_MAX) >= REPLY_MAX) {
|
||||
LOGE("reply is too long(too many files under '%s'\n", dir);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int copy_keyfile(const char *src, int src_type, const char *dstfile) {
|
||||
int srcfd = -1, dstfd;
|
||||
char buf[REPLY_MAX];
|
||||
|
||||
if ((src_type == IS_FILE) && (srcfd = open(src, O_RDONLY)) == -1) {
|
||||
LOGE("Cannot open the original file '%s'\n", src);
|
||||
return -1;
|
||||
}
|
||||
if ((dstfd = open(dstfile, O_CREAT|O_RDWR)) == -1) {
|
||||
LOGE("Cannot open the destination file '%s'\n", dstfile);
|
||||
return -1;
|
||||
}
|
||||
if (src_type == IS_FILE) {
|
||||
int length;
|
||||
while((length = read(srcfd, buf, REPLY_MAX)) > 0) {
|
||||
write(dstfd, buf, length);
|
||||
}
|
||||
} else {
|
||||
write(dstfd, src, strlen(src));
|
||||
}
|
||||
close(srcfd);
|
||||
close(dstfd);
|
||||
chmod(dstfile, 0440);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int install_key(const char *path, const char *certname, const char *src,
|
||||
int src_is_file, char *dstfile)
|
||||
{
|
||||
struct dirent *de;
|
||||
char fullpath[KEYNAME_LENGTH];
|
||||
DIR *d;
|
||||
|
||||
if (snprintf(fullpath, sizeof(fullpath), "%s/%s/", path, certname)
|
||||
>= KEYNAME_LENGTH) {
|
||||
LOGE("cert name '%s' is too long.\n", certname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((d = open_keystore(fullpath)) == NULL) {
|
||||
LOGE("Can not open the keystore '%s'\n", fullpath);
|
||||
return -1;
|
||||
}
|
||||
closedir(d);
|
||||
if (strlcat(fullpath, dstfile, KEYNAME_LENGTH) >= KEYNAME_LENGTH) {
|
||||
LOGE("cert name '%s' is too long.\n", certname);
|
||||
return -1;
|
||||
}
|
||||
return copy_keyfile(src, src_is_file, fullpath);
|
||||
}
|
||||
|
||||
static int get_key(const char *path, const char *keyname, const char *file,
|
||||
char reply[REPLY_MAX])
|
||||
{
|
||||
struct dirent *de;
|
||||
char filename[KEYNAME_LENGTH];
|
||||
int fd;
|
||||
|
||||
if (snprintf(filename, sizeof(filename), "%s/%s/%s", path, keyname, file)
|
||||
>= KEYNAME_LENGTH) {
|
||||
LOGE("cert name '%s' is too long.\n", keyname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((fd = open(filename, O_RDONLY)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
strlcpy(reply, filename, REPLY_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int remove_key(const char *dir, const char *key)
|
||||
{
|
||||
char dstfile[KEYNAME_LENGTH];
|
||||
char *keyfile[4] = { USER_KEY, USER_P12_CERT, USER_CERTIFICATE,
|
||||
CA_CERTIFICATE };
|
||||
int i, count = 0;
|
||||
|
||||
for ( i = 0 ; i < 4 ; i++) {
|
||||
if (snprintf(dstfile, KEYNAME_LENGTH, "%s/%s/%s", dir, key, keyfile[i])
|
||||
>= KEYNAME_LENGTH) {
|
||||
LOGE("keyname is too long '%s'\n", key);
|
||||
return -1;
|
||||
}
|
||||
if (unlink(dstfile) == 0) count++;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
LOGE("can not clean up '%s' keys or not exist\n", key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(dstfile, KEYNAME_LENGTH, "%s/%s", dir, key);
|
||||
if (rmdir(dstfile)) {
|
||||
LOGE("can not clean up '%s' directory\n", key);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int list_user_certs(char reply[REPLY_MAX])
|
||||
{
|
||||
return list_files(CERTS_DIR, reply);
|
||||
}
|
||||
|
||||
int list_ca_certs(char reply[REPLY_MAX])
|
||||
{
|
||||
return list_files(CACERTS_DIR, reply);
|
||||
}
|
||||
|
||||
int install_user_cert(const char *keyname, const char *cert, const char *key)
|
||||
{
|
||||
if (install_key(CERTS_DIR, keyname, cert, IS_FILE, USER_CERTIFICATE) == 0) {
|
||||
return install_key(CERTS_DIR, keyname, key, IS_FILE, USER_KEY);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int install_ca_cert(const char *keyname, const char *certfile)
|
||||
{
|
||||
return install_key(CACERTS_DIR, keyname, certfile, IS_FILE, CA_CERTIFICATE);
|
||||
}
|
||||
|
||||
int install_p12_cert(const char *keyname, const char *certfile)
|
||||
{
|
||||
return install_key(CERTS_DIR, keyname, certfile, IS_FILE, USER_P12_CERT);
|
||||
}
|
||||
|
||||
int add_ca_cert(const char *keyname, const char *certificate)
|
||||
{
|
||||
return install_key(CACERTS_DIR, keyname, certificate, IS_CONTENT,
|
||||
CA_CERTIFICATE);
|
||||
}
|
||||
|
||||
int add_user_cert(const char *keyname, const char *certificate)
|
||||
{
|
||||
return install_key(CERTS_DIR, keyname, certificate, IS_CONTENT,
|
||||
USER_CERTIFICATE);
|
||||
}
|
||||
|
||||
int add_user_key(const char *keyname, const char *key)
|
||||
{
|
||||
return install_key(CERTS_DIR, keyname, key, IS_CONTENT, USER_KEY);
|
||||
}
|
||||
|
||||
int get_ca_cert(const char *keyname, char reply[REPLY_MAX])
|
||||
{
|
||||
return get_key(CACERTS_DIR, keyname, CA_CERTIFICATE, reply);
|
||||
}
|
||||
|
||||
int get_user_cert(const char *keyname, char reply[REPLY_MAX])
|
||||
{
|
||||
return get_key(CERTS_DIR, keyname, USER_CERTIFICATE, reply);
|
||||
}
|
||||
|
||||
int get_user_key(const char *keyname, char reply[REPLY_MAX])
|
||||
{
|
||||
if(get_key(CERTS_DIR, keyname, USER_KEY, reply))
|
||||
return get_key(CERTS_DIR, keyname, USER_P12_CERT, reply);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remove_user_cert(const char *key)
|
||||
{
|
||||
return remove_key(CERTS_DIR, key);
|
||||
}
|
||||
|
||||
int remove_ca_cert(const char *key)
|
||||
{
|
||||
return remove_key(CACERTS_DIR, key);
|
||||
}
|
||||
60
cmds/keystore/common.h
Normal file
60
cmds/keystore/common.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
**
|
||||
** 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 __COMMON_H__
|
||||
#define __COMMON_H__
|
||||
|
||||
#define SOCKET_PATH "keystore"
|
||||
#define KEYSTORE_DIR "/data/misc/keystore/"
|
||||
|
||||
#define READ_TIMEOUT 3
|
||||
#define MAX_KEY_NAME_LENGTH 64
|
||||
#define MAX_NAMESPACE_LENGTH MAX_KEY_NAME_LENGTH
|
||||
#define MAX_KEY_VALUE_LENGTH 4096
|
||||
|
||||
#define BUFFER_MAX MAX_KEY_VALUE_LENGTH
|
||||
|
||||
typedef enum {
|
||||
BOOTUP,
|
||||
UNINITIALIZED,
|
||||
LOCKED,
|
||||
UNLOCKED,
|
||||
} KEYSTORE_STATE;
|
||||
|
||||
typedef enum {
|
||||
LOCK,
|
||||
UNLOCK,
|
||||
PASSWD,
|
||||
GETSTATE,
|
||||
LISTKEYS,
|
||||
GET,
|
||||
PUT,
|
||||
REMOVE,
|
||||
RESET,
|
||||
MAX_OPCODE
|
||||
} KEYSTORE_OPCODE;
|
||||
|
||||
typedef struct {
|
||||
uint32_t len;
|
||||
union {
|
||||
uint32_t opcode;
|
||||
uint32_t retcode;
|
||||
};
|
||||
unsigned char data[BUFFER_MAX + 1];
|
||||
} LPC_MARSHAL;
|
||||
|
||||
#endif
|
||||
365
cmds/keystore/keymgmt.c
Normal file
365
cmds/keystore/keymgmt.c
Normal file
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <cutils/log.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "keymgmt.h"
|
||||
|
||||
static int retry_count = 0;
|
||||
static unsigned char iv[IV_LEN];
|
||||
static KEYSTORE_STATE state = BOOTUP;
|
||||
static AES_KEY encryptKey, decryptKey;
|
||||
|
||||
inline void unlock_keystore(unsigned char *master_key)
|
||||
{
|
||||
AES_set_encrypt_key(master_key, AES_KEY_LEN, &encryptKey);
|
||||
AES_set_decrypt_key(master_key, AES_KEY_LEN, &decryptKey);
|
||||
memset(master_key, 0, sizeof(master_key));
|
||||
state = UNLOCKED;
|
||||
}
|
||||
|
||||
inline void lock_keystore()
|
||||
{
|
||||
memset(&encryptKey, 0 , sizeof(AES_KEY));
|
||||
memset(&decryptKey, 0 , sizeof(AES_KEY));
|
||||
state = LOCKED;
|
||||
}
|
||||
|
||||
inline void get_encrypt_key(char *passwd, AES_KEY *key)
|
||||
{
|
||||
unsigned char user_key[USER_KEY_LEN];
|
||||
gen_key(passwd, user_key, USER_KEY_LEN);
|
||||
AES_set_encrypt_key(user_key, AES_KEY_LEN, key);
|
||||
}
|
||||
|
||||
inline void get_decrypt_key(char *passwd, AES_KEY *key)
|
||||
{
|
||||
unsigned char user_key[USER_KEY_LEN];
|
||||
gen_key(passwd, user_key, USER_KEY_LEN);
|
||||
AES_set_decrypt_key(user_key, AES_KEY_LEN, key);
|
||||
}
|
||||
|
||||
static int gen_random_blob(unsigned char *key, int size)
|
||||
{
|
||||
int ret = 0;
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd == -1) return -1;
|
||||
if (read(fd, key, size) != size) ret = -1;
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int encrypt_n_save(AES_KEY *enc_key, DATA_BLOB *blob,
|
||||
const char *keyfile)
|
||||
{
|
||||
int size, fd, ret = -1;
|
||||
unsigned char enc_blob[MAX_BLOB_LEN];
|
||||
|
||||
char tmpfile[KEYFILE_LEN];
|
||||
strcpy(tmpfile, keyfile);
|
||||
strcat(tmpfile, ".tmp");
|
||||
|
||||
// prepare the blob
|
||||
memcpy(blob->iv, iv, IV_LEN);
|
||||
blob->blob_size = get_blob_size(blob);
|
||||
memcpy(enc_blob, blob->blob, blob->blob_size);
|
||||
AES_cbc_encrypt((unsigned char *)enc_blob, (unsigned char *)blob->blob,
|
||||
blob->blob_size, enc_key, iv, AES_ENCRYPT);
|
||||
// write to keyfile
|
||||
size = data_blob_size(blob);
|
||||
if ((fd = open(tmpfile, O_CREAT|O_RDWR)) == -1) return -1;
|
||||
if (write(fd, blob, size) == size) ret = 0;
|
||||
close(fd);
|
||||
if (!ret) {
|
||||
unlink(keyfile);
|
||||
rename(tmpfile, keyfile);
|
||||
chmod(keyfile, 0440);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int load_n_decrypt(const char *keyname, const char *keyfile,
|
||||
AES_KEY *key, DATA_BLOB *blob)
|
||||
{
|
||||
int fd, ret = -1;
|
||||
if ((fd = open(keyfile, O_RDONLY)) == -1) return -1;
|
||||
// get the encrypted blob and iv
|
||||
if ((read(fd, blob->iv, sizeof(blob->iv)) != sizeof(blob->iv)) ||
|
||||
(read(fd, &blob->blob_size, sizeof(uint32_t)) != sizeof(uint32_t)) ||
|
||||
(blob->blob_size > MAX_BLOB_LEN)) {
|
||||
goto err;
|
||||
} else {
|
||||
unsigned char enc_blob[MAX_BLOB_LEN];
|
||||
if (read(fd, enc_blob, blob->blob_size) !=
|
||||
(int) blob->blob_size) goto err;
|
||||
// decrypt the blob
|
||||
AES_cbc_encrypt((unsigned char *)enc_blob, (unsigned char*)blob->blob,
|
||||
blob->blob_size, key, blob->iv, AES_DECRYPT);
|
||||
if (strcmp(keyname, (char*)blob->keyname) == 0) ret = 0;
|
||||
}
|
||||
err:
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int store_master_key(char *upasswd, unsigned char *master_key)
|
||||
{
|
||||
AES_KEY key;
|
||||
DATA_BLOB blob;
|
||||
|
||||
// prepare the blob
|
||||
strlcpy(blob.keyname, MASTER_KEY_TAG, USER_KEY_LEN);
|
||||
blob.value_size = USER_KEY_LEN;
|
||||
memcpy((void*)blob.value, (const void*)master_key, USER_KEY_LEN);
|
||||
|
||||
// generate the encryption key
|
||||
get_encrypt_key(upasswd, &key);
|
||||
return encrypt_n_save(&key, &blob, MASTER_KEY);
|
||||
}
|
||||
|
||||
static int get_master_key(char *upasswd, unsigned char *master_key)
|
||||
{
|
||||
AES_KEY key;
|
||||
int size, ret = 0;
|
||||
DATA_BLOB blob;
|
||||
|
||||
get_decrypt_key(upasswd, &key);
|
||||
ret = load_n_decrypt(MASTER_KEY_TAG, MASTER_KEY, &key, &blob);
|
||||
if (!ret) memcpy(master_key, blob.value, blob.value_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int create_master_key(char *upasswd)
|
||||
{
|
||||
int ret;
|
||||
unsigned char mpasswd[AES_KEY_LEN];
|
||||
unsigned char master_key[USER_KEY_LEN];
|
||||
|
||||
gen_random_blob(mpasswd, AES_KEY_LEN);
|
||||
gen_key((char*)mpasswd, master_key, USER_KEY_LEN);
|
||||
if ((ret = store_master_key(upasswd, master_key)) == 0) {
|
||||
unlock_keystore(master_key);
|
||||
}
|
||||
memset(master_key, 0, USER_KEY_LEN);
|
||||
memset(mpasswd, 0, AES_KEY_LEN);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int change_passwd(char *data)
|
||||
{
|
||||
unsigned char master_key[USER_KEY_LEN];
|
||||
char *old_pass, *new_pass = NULL, *p, *delimiter=" ";
|
||||
int ret, count = 0;
|
||||
char *context = NULL;
|
||||
|
||||
old_pass = p = strtok_r(data, delimiter, &context);
|
||||
while (p != NULL) {
|
||||
count++;
|
||||
new_pass = p;
|
||||
p = strtok_r(NULL, delimiter, &context);
|
||||
}
|
||||
if (count != 2) return -1;
|
||||
if ((ret = get_master_key(old_pass, master_key)) == 0) {
|
||||
ret = store_master_key(new_pass, master_key);
|
||||
retry_count = 0;
|
||||
} else {
|
||||
ret = MAX_RETRY_COUNT - ++retry_count;
|
||||
if (ret == 0) {
|
||||
retry_count = 0;
|
||||
LOGE("passwd:reach max retry count, reset the keystore now.");
|
||||
reset_keystore();
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int remove_key(const char *namespace, const char *keyname)
|
||||
{
|
||||
char keyfile[KEYFILE_LEN];
|
||||
|
||||
if (state != UNLOCKED) return -state;
|
||||
sprintf(keyfile, KEYFILE_NAME, namespace, keyname);
|
||||
return unlink(keyfile);
|
||||
}
|
||||
|
||||
int put_key(const char *namespace, const char *keyname,
|
||||
unsigned char *data, int size)
|
||||
{
|
||||
DATA_BLOB blob;
|
||||
uint32_t real_size;
|
||||
char keyfile[KEYFILE_LEN];
|
||||
|
||||
if (state != UNLOCKED) {
|
||||
LOGE("Can not store key with current state %d\n", state);
|
||||
return -state;
|
||||
}
|
||||
sprintf(keyfile, KEYFILE_NAME, namespace, keyname);
|
||||
// flatten the args
|
||||
strcpy(blob.keyname, keyname);
|
||||
blob.value_size = size;
|
||||
memcpy(blob.value, data, size);
|
||||
return encrypt_n_save(&encryptKey, &blob, keyfile);
|
||||
}
|
||||
|
||||
int get_key(const char *namespace, const char *keyname,
|
||||
unsigned char *data, int *size)
|
||||
{
|
||||
int ret;
|
||||
DATA_BLOB blob;
|
||||
uint32_t blob_size;
|
||||
char keyfile[KEYFILE_LEN];
|
||||
|
||||
if (state != UNLOCKED) {
|
||||
LOGE("Can not retrieve key value with current state %d\n", state);
|
||||
return -state;
|
||||
}
|
||||
sprintf(keyfile, KEYFILE_NAME, namespace, keyname);
|
||||
ret = load_n_decrypt(keyname, keyfile, &decryptKey, &blob);
|
||||
if (!ret) {
|
||||
if ((blob.value_size > MAX_KEY_VALUE_LENGTH)) {
|
||||
ret = -1;
|
||||
} else {
|
||||
*size = blob.value_size;
|
||||
memcpy(data, blob.value, *size);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int list_keys(const char *namespace, char reply[BUFFER_MAX])
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *de;
|
||||
|
||||
if (!namespace || ((d = opendir("."))) == NULL) {
|
||||
LOGE("cannot open keystore dir or namespace is null\n");
|
||||
return -1;
|
||||
}
|
||||
while ((de = readdir(d))) {
|
||||
char *prefix, *name, *keyfile = de->d_name;
|
||||
char *context = NULL;
|
||||
|
||||
if (de->d_type != DT_REG) continue;
|
||||
if ((prefix = strtok_r(keyfile, NAME_DELIMITER, &context))
|
||||
== NULL) continue;
|
||||
if (strcmp(prefix, namespace)) continue;
|
||||
if ((name = strtok_r(NULL, NAME_DELIMITER, &context)) == NULL) continue;
|
||||
// append the key name into reply
|
||||
if (reply[0] != 0) strlcat(reply, " ", BUFFER_MAX);
|
||||
if (strlcat(reply, name, BUFFER_MAX) >= BUFFER_MAX) {
|
||||
LOGE("too many files under keystore directory\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int passwd(char *data)
|
||||
{
|
||||
if (state == UNINITIALIZED) {
|
||||
if (strchr(data, ' ')) return -1;
|
||||
return create_master_key(data);
|
||||
}
|
||||
return change_passwd(data);
|
||||
}
|
||||
|
||||
int lock()
|
||||
{
|
||||
switch(state) {
|
||||
case UNLOCKED:
|
||||
lock_keystore();
|
||||
case LOCKED:
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int unlock(char *passwd)
|
||||
{
|
||||
unsigned char master_key[USER_KEY_LEN];
|
||||
int ret = get_master_key(passwd, master_key);
|
||||
if (!ret) {
|
||||
unlock_keystore(master_key);
|
||||
retry_count = 0;
|
||||
} else {
|
||||
ret = MAX_RETRY_COUNT - ++retry_count;
|
||||
if (ret == 0) {
|
||||
retry_count = 0;
|
||||
LOGE("unlock:reach max retry count, reset the keystore now.");
|
||||
reset_keystore();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
KEYSTORE_STATE get_state()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
int reset_keystore()
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *de;
|
||||
|
||||
if ((d = opendir(".")) == NULL) {
|
||||
LOGE("cannot open keystore dir\n");
|
||||
return -1;
|
||||
}
|
||||
while ((de = readdir(d))) unlink(de->d_name);
|
||||
closedir(d);
|
||||
state = UNINITIALIZED;
|
||||
LOGI("keystore is reset.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_keystore(const char *dir)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (!dir) mkdir(dir, 0770);
|
||||
if (!dir || chdir(dir)) {
|
||||
LOGE("Can not open/create the keystore directory %s\n",
|
||||
dir ? dir : "(null)");
|
||||
return -1;
|
||||
}
|
||||
gen_random_blob(iv, IV_LEN);
|
||||
if ((fd = open(MASTER_KEY, O_RDONLY)) == -1) {
|
||||
state = UNINITIALIZED;
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
state = LOCKED;
|
||||
return 0;
|
||||
}
|
||||
81
cmds/keystore/keymgmt.h
Normal file
81
cmds/keystore/keymgmt.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
** 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 __KEYMGMT_H__
|
||||
#define __KEYMGMT_H__
|
||||
|
||||
#define MASTER_KEY_TAG "master_key"
|
||||
#define MASTER_KEY ".keymaster"
|
||||
#define MAX_PATH_LEN 128
|
||||
#define SALT "Android Keystore 0.1"
|
||||
#define NAME_DELIMITER "_"
|
||||
#define KEYFILE_NAME "%s"NAME_DELIMITER"%s"
|
||||
#define KEYGEN_ITER 1024
|
||||
#define AES_KEY_LEN 128
|
||||
#define USER_KEY_LEN (AES_KEY_LEN/8)
|
||||
#define IV_LEN USER_KEY_LEN
|
||||
#define MAX_RETRY_COUNT 6
|
||||
|
||||
#define gen_key(passwd, key, len) \
|
||||
PKCS5_PBKDF2_HMAC_SHA1(passwd, strlen(passwd), \
|
||||
(unsigned char*)SALT, \
|
||||
strlen(SALT), KEYGEN_ITER, \
|
||||
len, key)
|
||||
|
||||
#define KEYFILE_LEN MAX_NAMESPACE_LENGTH + MAX_KEY_NAME_LENGTH + 6
|
||||
|
||||
#define get_blob_size(blob) \
|
||||
(((blob->value_size + sizeof(uint32_t) + MAX_KEY_NAME_LENGTH \
|
||||
+ USER_KEY_LEN - 1) / USER_KEY_LEN) * USER_KEY_LEN)
|
||||
|
||||
#define MAX_BLOB_LEN ((MAX_KEY_VALUE_LENGTH + MAX_KEY_NAME_LENGTH + \
|
||||
sizeof(uint32_t) + USER_KEY_LEN - 1) / USER_KEY_LEN)\
|
||||
* USER_KEY_LEN
|
||||
|
||||
#define data_blob_size(blob) USER_KEY_LEN + sizeof(uint32_t) + blob->blob_size
|
||||
|
||||
typedef struct {
|
||||
unsigned char iv[USER_KEY_LEN];
|
||||
uint32_t blob_size;
|
||||
union {
|
||||
unsigned char blob[1];
|
||||
struct {
|
||||
uint32_t value_size;
|
||||
char keyname[MAX_KEY_NAME_LENGTH];
|
||||
unsigned char value[MAX_KEY_VALUE_LENGTH];
|
||||
} __attribute__((packed));
|
||||
};
|
||||
} DATA_BLOB;
|
||||
|
||||
typedef struct {
|
||||
char tag[USER_KEY_LEN];
|
||||
unsigned char master_key[USER_KEY_LEN];
|
||||
} MASTER_BLOB;
|
||||
|
||||
int put_key(const char *namespace, const char *keyname,
|
||||
unsigned char *data, int size);
|
||||
int get_key(const char *namespace, const char *keyname,
|
||||
unsigned char *data, int *size);
|
||||
int remove_key(const char *namespace, const char *keyname);
|
||||
int list_keys(const char *namespace, char reply[BUFFER_MAX]);
|
||||
int passwd(char *data);
|
||||
int lock();
|
||||
int unlock(char *passwd);
|
||||
KEYSTORE_STATE get_state();
|
||||
int reset_keystore();
|
||||
int init_keystore(const char *dir);
|
||||
|
||||
#endif
|
||||
@@ -1,315 +0,0 @@
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
|
||||
#include "keystore.h"
|
||||
|
||||
static inline int has_whitespace(char *name)
|
||||
{
|
||||
if((strrchr(name, ' ') != NULL)) {
|
||||
LOGE("'%s' contains whitespace character\n", name);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_list_user_certs(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return list_user_certs(reply);
|
||||
}
|
||||
|
||||
static int do_list_ca_certs(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return list_ca_certs(reply);
|
||||
}
|
||||
|
||||
static int do_install_user_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
/* copy the certificate and key to keystore */
|
||||
return install_user_cert(arg[0], arg[1], arg[2]);
|
||||
}
|
||||
|
||||
static int do_install_p12_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
return install_p12_cert(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
static int do_install_ca_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
/* copy the certificate and key to keystore */
|
||||
return install_ca_cert(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
static int do_add_ca_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
return add_ca_cert(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
static int do_add_user_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
return add_user_cert(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
static int do_add_user_key(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
if (has_whitespace(arg[0])) return -1;
|
||||
return add_user_key(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
static int do_get_ca_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return get_ca_cert(arg[0], reply);
|
||||
}
|
||||
|
||||
static int do_get_user_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return get_user_cert(arg[0], reply);
|
||||
}
|
||||
|
||||
static int do_get_user_key(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return get_user_key(arg[0], reply);
|
||||
}
|
||||
|
||||
static int do_remove_user_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return remove_user_cert(arg[0]);
|
||||
}
|
||||
|
||||
static int do_remove_ca_cert(char **arg, char reply[REPLY_MAX])
|
||||
{
|
||||
return remove_ca_cert(arg[0]);
|
||||
}
|
||||
|
||||
|
||||
struct cmdinfo {
|
||||
const char *name;
|
||||
unsigned numargs;
|
||||
int (*func)(char **arg, char reply[REPLY_MAX]);
|
||||
};
|
||||
|
||||
|
||||
struct cmdinfo cmds[] = {
|
||||
{ "listcacerts", 0, do_list_ca_certs },
|
||||
{ "listusercerts", 0, do_list_user_certs },
|
||||
{ "installusercert", 3, do_install_user_cert },
|
||||
{ "installcacert", 2, do_install_ca_cert },
|
||||
{ "installp12cert", 2, do_install_p12_cert },
|
||||
{ "addusercert", 2, do_add_user_cert },
|
||||
{ "adduserkey", 2, do_add_user_key },
|
||||
{ "addcacert", 2, do_add_ca_cert },
|
||||
{ "getusercert", 1, do_get_user_cert },
|
||||
{ "getuserkey", 1, do_get_user_key },
|
||||
{ "getcacert", 1, do_get_ca_cert },
|
||||
{ "removecacert", 1, do_remove_ca_cert },
|
||||
{ "removeusercert", 1, do_remove_user_cert },
|
||||
};
|
||||
|
||||
static int readx(int s, void *_buf, int count)
|
||||
{
|
||||
char *buf = _buf;
|
||||
int n = 0, r;
|
||||
if (count < 0) return -1;
|
||||
while (n < count) {
|
||||
r = read(s, buf + n, count - n);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
LOGE("read error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (r == 0) {
|
||||
LOGE("eof\n");
|
||||
return -1; /* EOF */
|
||||
}
|
||||
n += r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int writex(int s, const void *_buf, int count)
|
||||
{
|
||||
const char *buf = _buf;
|
||||
int n = 0, r;
|
||||
if (count < 0) return -1;
|
||||
while (n < count) {
|
||||
r = write(s, buf + n, count - n);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
LOGE("write error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
n += r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Tokenize the command buffer, locate a matching command,
|
||||
* ensure that the required number of arguments are provided,
|
||||
* call the function(), return the result.
|
||||
*/
|
||||
static int execute(int s, char cmd[BUFFER_MAX])
|
||||
{
|
||||
char reply[REPLY_MAX];
|
||||
char *arg[TOKEN_MAX+1];
|
||||
unsigned i;
|
||||
unsigned n = 0;
|
||||
unsigned short count;
|
||||
short ret = -1;
|
||||
|
||||
/* default reply is "" */
|
||||
reply[0] = 0;
|
||||
|
||||
/* n is number of args (not counting arg[0]) */
|
||||
arg[0] = cmd;
|
||||
while (*cmd) {
|
||||
if (*cmd == CMD_DELIMITER) {
|
||||
*cmd++ = 0;
|
||||
n++;
|
||||
arg[n] = cmd;
|
||||
if (n == TOKEN_MAX) {
|
||||
LOGE("too many arguments\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
cmd++;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
|
||||
if (!strcmp(cmds[i].name,arg[0])) {
|
||||
if (n != cmds[i].numargs) {
|
||||
LOGE("%s requires %d arguments (%d given)\n",
|
||||
cmds[i].name, cmds[i].numargs, n);
|
||||
} else {
|
||||
ret = (short) cmds[i].func(arg + 1, reply);
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
LOGE("unsupported command '%s'\n", arg[0]);
|
||||
|
||||
done:
|
||||
if (reply[0]) {
|
||||
strlcpy(cmd, reply, BUFFER_MAX);
|
||||
count = strlen(cmd);
|
||||
} else {
|
||||
count = 0;
|
||||
}
|
||||
if (writex(s, &ret, sizeof(ret))) return -1;
|
||||
if (ret == 0) {
|
||||
if (writex(s, &count, sizeof(count))) return -1;
|
||||
if (writex(s, cmd, count)) return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int shell_command(const int argc, const char **argv)
|
||||
{
|
||||
int fd, i;
|
||||
short ret;
|
||||
unsigned short count;
|
||||
char delimiter[2] = { CMD_DELIMITER, 0 };
|
||||
char buf[BUFFER_MAX]="";
|
||||
|
||||
fd = socket_local_client(SOCKET_PATH,
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED,
|
||||
SOCK_STREAM);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "Keystore service is not up and running\n");
|
||||
exit(1);
|
||||
}
|
||||
for(i = 0; i < argc; i++) {
|
||||
if (i > 0) strlcat(buf, delimiter, BUFFER_MAX);
|
||||
if(strlcat(buf, argv[i], BUFFER_MAX) >= BUFFER_MAX) {
|
||||
fprintf(stderr, "Arguments are too long\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
count = strlen(buf);
|
||||
if (writex(fd, &count, sizeof(count))) return -1;
|
||||
if (writex(fd, buf, strlen(buf))) return -1;
|
||||
if (readx(fd, &ret, sizeof(ret))) return -1;
|
||||
if (ret == 0) {
|
||||
if (readx(fd, &count, sizeof(count))) return -1;
|
||||
if (readx(fd, buf, count)) return -1;
|
||||
buf[count]=0;
|
||||
fprintf(stdout, "%s\n", buf);
|
||||
} else {
|
||||
fprintf(stderr, "Failed, please check log!\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(const int argc, const char *argv[])
|
||||
{
|
||||
char buf[BUFFER_MAX];
|
||||
struct sockaddr addr;
|
||||
socklen_t alen;
|
||||
int lsocket, s, count;
|
||||
|
||||
if (argc > 1) {
|
||||
return shell_command(argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
lsocket = android_get_control_socket(SOCKET_PATH);
|
||||
if (lsocket < 0) {
|
||||
LOGE("Failed to get socket from environment: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
if (listen(lsocket, 5)) {
|
||||
LOGE("Listen on socket failed: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
for (;;) {
|
||||
alen = sizeof(addr);
|
||||
s = accept(lsocket, &addr, &alen);
|
||||
if (s < 0) {
|
||||
LOGE("Accept failed: %s\n", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
fcntl(s, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
LOGI("new connection\n");
|
||||
for (;;) {
|
||||
unsigned short count;
|
||||
if (readx(s, &count, sizeof(count))) {
|
||||
LOGE("failed to read size\n");
|
||||
break;
|
||||
}
|
||||
if ((count < 1) || (count >= BUFFER_MAX)) {
|
||||
LOGE("invalid size %d\n", count);
|
||||
break;
|
||||
}
|
||||
if (readx(s, buf, count)) {
|
||||
LOGE("failed to read command\n");
|
||||
break;
|
||||
}
|
||||
buf[count] = 0;
|
||||
if (execute(s, buf)) break;
|
||||
}
|
||||
LOGI("closing connection\n");
|
||||
close(s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
**
|
||||
** 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 "keystore"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <utime.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/log.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#define SOCKET_PATH "keystore"
|
||||
|
||||
|
||||
/* path of the keystore */
|
||||
|
||||
#define KEYSTORE_DIR_PREFIX "/data/misc/keystore"
|
||||
#define CERTS_DIR KEYSTORE_DIR_PREFIX "/keys"
|
||||
#define CACERTS_DIR KEYSTORE_DIR_PREFIX "/cacerts"
|
||||
#define CA_CERTIFICATE "ca.crt"
|
||||
#define USER_CERTIFICATE "user.crt"
|
||||
#define USER_P12_CERT "user.p12"
|
||||
#define USER_KEY "user.key"
|
||||
#define DOT "."
|
||||
#define DOTDOT ".."
|
||||
|
||||
#define BUFFER_MAX 4096 /* input buffer for commands */
|
||||
#define TOKEN_MAX 8 /* max number of arguments in buffer */
|
||||
#define REPLY_MAX 4096 /* largest reply allowed */
|
||||
#define CMD_DELIMITER '\t'
|
||||
#define KEYNAME_LENGTH 128
|
||||
#define IS_CONTENT 0
|
||||
#define IS_FILE 1
|
||||
|
||||
|
||||
/* commands.c */
|
||||
int list_ca_certs(char reply[REPLY_MAX]);
|
||||
int list_user_certs(char reply[REPLY_MAX]);
|
||||
int install_user_cert(const char *certname, const char *cert, const char *key);
|
||||
int install_ca_cert(const char *certname, const char *cert);
|
||||
int install_p12_cert(const char *certname, const char *cert);
|
||||
int add_ca_cert(const char *certname, const char *content);
|
||||
int add_user_cert(const char *certname, const char *content);
|
||||
int add_user_key(const char *keyname, const char *content);
|
||||
int get_ca_cert(const char *keyname, char reply[REPLY_MAX]);
|
||||
int get_user_cert(const char *keyname, char reply[REPLY_MAX]);
|
||||
int get_user_key(const char *keyname, char reply[REPLY_MAX]);
|
||||
int remove_user_cert(const char *certname);
|
||||
int remove_ca_cert(const char *certname);
|
||||
409
cmds/keystore/netkeystore.c
Normal file
409
cmds/keystore/netkeystore.c
Normal file
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
** 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 "keystore"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <utime.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/log.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include "netkeystore.h"
|
||||
#include "keymgmt.h"
|
||||
|
||||
#define CMD_PUT_WITH_FILE "putfile"
|
||||
|
||||
typedef void CMD_FUNC(LPC_MARSHAL *cmd, LPC_MARSHAL *reply);
|
||||
|
||||
struct cmdinfo {
|
||||
const char *name;
|
||||
CMD_FUNC *func;
|
||||
};
|
||||
|
||||
static CMD_FUNC do_lock;
|
||||
static CMD_FUNC do_unlock;
|
||||
static CMD_FUNC do_passwd;
|
||||
static CMD_FUNC do_get_state;;
|
||||
static CMD_FUNC do_listkeys;
|
||||
static CMD_FUNC do_get_key;
|
||||
static CMD_FUNC do_put_key;
|
||||
static CMD_FUNC do_remove_key;
|
||||
static CMD_FUNC do_reset_keystore;
|
||||
|
||||
#define str(x) #x
|
||||
|
||||
struct cmdinfo cmds[] = {
|
||||
{ str(LOCK), do_lock },
|
||||
{ str(UNLOCK), do_unlock },
|
||||
{ str(PASSWD), do_passwd },
|
||||
{ str(GETSTATE), do_get_state },
|
||||
{ str(LISTKEYS), do_listkeys },
|
||||
{ str(GET), do_get_key },
|
||||
{ str(PUT), do_put_key },
|
||||
{ str(REMOVE), do_remove_key },
|
||||
{ str(RESET), do_reset_keystore },
|
||||
};
|
||||
|
||||
static struct ucred cr;
|
||||
|
||||
static int check_get_perm(int uid)
|
||||
{
|
||||
if (uid == AID_WIFI || uid == AID_VPN) return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int check_reset_perm(int uid)
|
||||
{
|
||||
if (uid == AID_SYSTEM) return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_keyname(char *name, uint32_t len,
|
||||
char *namespace, char *keyname)
|
||||
{
|
||||
int count = 0;
|
||||
char *c = namespace, *p = namespace, *t = name;
|
||||
|
||||
if (!name || !namespace || !keyname) return -1;
|
||||
while (t < name + len && (*t != 0)) {
|
||||
if (*t == ' ') {
|
||||
if (c == keyname) return -1;
|
||||
*p = count = 0;
|
||||
c = p = keyname;
|
||||
t++;
|
||||
} else {
|
||||
if (!isalnum(*t)) return -1;
|
||||
*p++ = *t++;
|
||||
// also check if the keyname/namespace is too long.
|
||||
if (count++ == MAX_KEY_NAME_LENGTH) return -1;
|
||||
}
|
||||
}
|
||||
*p = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// args of passwd():
|
||||
// firstPassword - for the first time
|
||||
// oldPassword newPassword - for changing the password
|
||||
static void do_passwd(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
reply->retcode = passwd((char*)cmd->data);
|
||||
}
|
||||
|
||||
// args of lock():
|
||||
// no argument
|
||||
static void do_lock(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
reply->retcode = lock();
|
||||
}
|
||||
|
||||
// args of unlock():
|
||||
// password
|
||||
static void do_unlock(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
reply->retcode = unlock((char*)cmd->data);
|
||||
}
|
||||
|
||||
// args of get_state():
|
||||
// no argument
|
||||
static void do_get_state(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
reply->retcode = get_state();
|
||||
}
|
||||
|
||||
// args of listkeys():
|
||||
// namespace
|
||||
static void do_listkeys(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
reply->retcode = list_keys((const char*)cmd->data, (char*)reply->data);
|
||||
if (!reply->retcode) reply->len = strlen((char*)reply->data) + 1;
|
||||
}
|
||||
|
||||
// args of get():
|
||||
// namespace keyname
|
||||
static void do_get_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
char namespace[MAX_KEY_NAME_LENGTH];
|
||||
char keyname[MAX_KEY_NAME_LENGTH];
|
||||
|
||||
if (check_get_perm(cr.uid)) {
|
||||
LOGE("uid %d doesn't have the permission to get key value\n", cr.uid);
|
||||
reply->retcode = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (parse_keyname((char*)cmd->data, cmd->len, namespace, keyname)) {
|
||||
reply->retcode = -1;
|
||||
} else {
|
||||
reply->retcode = get_key(namespace, keyname, reply->data,
|
||||
(int*)&reply->len);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_value_index(LPC_MARSHAL *cmd)
|
||||
{
|
||||
uint32_t count = 0, i;
|
||||
for (i = 0 ; i < cmd->len ; ++i) {
|
||||
if (cmd->data[i] == ' ') {
|
||||
if (++count == 2) return ++i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// args of put():
|
||||
// namespace keyname keyvalue
|
||||
static void do_put_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
char namespace[MAX_KEY_NAME_LENGTH];
|
||||
char keyname[MAX_KEY_NAME_LENGTH];
|
||||
|
||||
int p = get_value_index(cmd);
|
||||
if (p == -1) {
|
||||
reply->retcode = -1;
|
||||
} else {
|
||||
unsigned char *value;
|
||||
if (parse_keyname((char*)cmd->data, p - 1, namespace, keyname)) {
|
||||
reply->retcode = -1;
|
||||
return;
|
||||
}
|
||||
value = &cmd->data[p];
|
||||
int len = cmd->len - p;
|
||||
reply->retcode = put_key(namespace, keyname, value, len);
|
||||
}
|
||||
}
|
||||
|
||||
// args of remove_key():
|
||||
// namespace keyname
|
||||
static void do_remove_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
char namespace[MAX_KEY_NAME_LENGTH];
|
||||
char keyname[MAX_KEY_NAME_LENGTH];
|
||||
if (parse_keyname((char*)cmd->data, cmd->len, namespace, keyname)) {
|
||||
reply->retcode = -1;
|
||||
return;
|
||||
}
|
||||
reply->retcode = remove_key(namespace, keyname);
|
||||
}
|
||||
|
||||
// args of reset_keystore():
|
||||
// no argument
|
||||
static void do_reset_keystore(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
if (check_reset_perm(cr.uid)) {
|
||||
LOGE("uid %d doesn't have the permission to reset the keystore\n",
|
||||
cr.uid);
|
||||
reply->retcode = -1;
|
||||
return;
|
||||
}
|
||||
reply->retcode = reset_keystore();
|
||||
}
|
||||
static void execute(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
|
||||
{
|
||||
uint32_t cmd_max = sizeof(cmds)/sizeof(struct cmdinfo);
|
||||
|
||||
if (cmd->opcode >= cmd_max) {
|
||||
LOGE("the opcode (%d) is not valid", cmd->opcode);
|
||||
reply->retcode = -1;
|
||||
return;
|
||||
}
|
||||
cmds[cmd->opcode].func(cmd, reply);
|
||||
}
|
||||
|
||||
static int set_read_timeout(int socket)
|
||||
{
|
||||
struct timeval tv;
|
||||
tv.tv_sec = READ_TIMEOUT;
|
||||
if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv))
|
||||
{
|
||||
LOGE("setsockopt failed");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_input_from_file(const char *filename, LPC_MARSHAL *cmd)
|
||||
{
|
||||
int fd, len, ret = 0;
|
||||
|
||||
// get opcode of the function put()
|
||||
if ((fd = open(filename, O_RDONLY)) == -1) {
|
||||
fprintf(stderr, "Can not open file %s\n", filename);
|
||||
return -1;
|
||||
}
|
||||
cmd->data[cmd->len] = ' ';
|
||||
cmd->len++;
|
||||
len = read(fd, cmd->data + cmd->len, BUFFER_MAX - cmd->len);
|
||||
if (len < 0 || (len == (int)(BUFFER_MAX - cmd->len))) {
|
||||
ret = -1;
|
||||
} else {
|
||||
cmd->len += len;
|
||||
}
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int flatten_str_args(int argc, const char **argv, LPC_MARSHAL *cmd)
|
||||
{
|
||||
int i, len = 0;
|
||||
char *buf = (char*)cmd->data;
|
||||
buf[0] = 0;
|
||||
for (i = 0 ; i < argc ; ++i) {
|
||||
if (i == 0) {
|
||||
len = strlcpy(buf, argv[i], BUFFER_MAX);
|
||||
} else {
|
||||
len += snprintf(buf + len, BUFFER_MAX - len, " %s", argv[i]);
|
||||
}
|
||||
if (len >= BUFFER_MAX) return -1;
|
||||
}
|
||||
if (len) cmd->len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_cmd(int argc, const char **argv, LPC_MARSHAL *cmd)
|
||||
{
|
||||
uint32_t i, len = 0;
|
||||
uint32_t cmd_max = sizeof(cmds)/sizeof(cmds[0]);
|
||||
|
||||
for (i = 0 ; i < cmd_max ; ++i) {
|
||||
if (!strcasecmp(argv[0], cmds[i].name)) break;
|
||||
}
|
||||
|
||||
if (i == cmd_max) {
|
||||
// check if this is a command to put the key value with a file.
|
||||
if (strcmp(argv[0], CMD_PUT_WITH_FILE) != 0) return -1;
|
||||
cmd->opcode = PUT;
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "%s args\n\tnamespace keyname filename\n",
|
||||
argv[0]);
|
||||
return -1;
|
||||
}
|
||||
if (flatten_str_args(argc - 2, argv + 1, cmd)) return -1;
|
||||
return append_input_from_file(argv[3], cmd);
|
||||
} else {
|
||||
cmd->opcode = i;
|
||||
return flatten_str_args(argc - 1, argv + 1, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static int shell_command(const int argc, const char **argv)
|
||||
{
|
||||
int fd, i;
|
||||
LPC_MARSHAL cmd;
|
||||
|
||||
if (parse_cmd(argc, argv , &cmd)) {
|
||||
fprintf(stderr, "Incorrect command or command line is too long.\n");
|
||||
exit(1);
|
||||
}
|
||||
fd = socket_local_client(SOCKET_PATH,
|
||||
ANDROID_SOCKET_NAMESPACE_RESERVED,
|
||||
SOCK_STREAM);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "Keystore service is not up and running.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (write_marshal(fd, &cmd)) {
|
||||
fprintf(stderr, "Incorrect command or command line is too long.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (read_marshal(fd, &cmd)) {
|
||||
fprintf(stderr, "Failed to read the result.\n");
|
||||
exit(1);
|
||||
}
|
||||
cmd.data[cmd.len] = 0;
|
||||
fprintf(stdout, "%s\n", (cmd.retcode == 0) ? "Succeeded!" : "Failed!");
|
||||
if (cmd.len) fprintf(stdout, "\t%s\n", (char*)cmd.data);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(const int argc, const char *argv[])
|
||||
{
|
||||
struct sockaddr addr;
|
||||
socklen_t alen;
|
||||
int lsocket, s;
|
||||
LPC_MARSHAL cmd, reply;
|
||||
|
||||
if (argc > 1) {
|
||||
return shell_command(argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
if (init_keystore(KEYSTORE_DIR)) {
|
||||
LOGE("Can not initialize the keystore, the directory exist?\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
lsocket = android_get_control_socket(SOCKET_PATH);
|
||||
if (lsocket < 0) {
|
||||
LOGE("Failed to get socket from environment: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
if (listen(lsocket, 5)) {
|
||||
LOGE("Listen on socket failed: %s\n", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
|
||||
memset(&reply, 0, sizeof(LPC_MARSHAL));
|
||||
|
||||
for (;;) {
|
||||
socklen_t cr_size = sizeof(cr);
|
||||
alen = sizeof(addr);
|
||||
s = accept(lsocket, &addr, &alen);
|
||||
|
||||
/* retrieve the caller info here */
|
||||
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
|
||||
close(s);
|
||||
LOGE("Unable to recieve socket options\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s < 0) {
|
||||
LOGE("Accept failed: %s\n", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
fcntl(s, F_SETFD, FD_CLOEXEC);
|
||||
if (set_read_timeout(s)) {
|
||||
close(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
// read the command, execute and send the result back.
|
||||
if(read_marshal(s, &cmd)) goto err;
|
||||
LOGI("new connection\n");
|
||||
execute(&cmd, &reply);
|
||||
write_marshal(s, &reply);
|
||||
err:
|
||||
memset(&reply, 0, sizeof(LPC_MARSHAL));
|
||||
LOGI("closing connection\n");
|
||||
close(s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
96
cmds/keystore/netkeystore.h
Normal file
96
cmds/keystore/netkeystore.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
**
|
||||
** 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 __NETKEYSTORE_H__
|
||||
#define __NETKEYSTORE_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/log.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
static inline int readx(int s, void *_buf, int count)
|
||||
{
|
||||
char *buf = _buf;
|
||||
int n = 0, r;
|
||||
if (count < 0) return -1;
|
||||
while (n < count) {
|
||||
r = read(s, buf + n, count - n);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
LOGE("read error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (r == 0) {
|
||||
LOGE("eof\n");
|
||||
return -1; /* EOF */
|
||||
}
|
||||
n += r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int writex(int s, const void *_buf, int count)
|
||||
{
|
||||
const char *buf = _buf;
|
||||
int n = 0, r;
|
||||
if (count < 0) return -1;
|
||||
while (n < count) {
|
||||
r = write(s, buf + n, count - n);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
LOGE("write error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
n += r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int read_marshal(int s, LPC_MARSHAL *cmd)
|
||||
{
|
||||
if (readx(s, cmd, 2 * sizeof(uint32_t))) {
|
||||
LOGE("failed to read header\n");
|
||||
return -1;
|
||||
}
|
||||
if (cmd->len > BUFFER_MAX) {
|
||||
LOGE("invalid size %d\n", cmd->len);
|
||||
return -1;
|
||||
}
|
||||
if (readx(s, cmd->data, cmd->len)) {
|
||||
LOGE("failed to read data\n");
|
||||
return -1;
|
||||
}
|
||||
cmd->data[cmd->len] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int write_marshal(int s, LPC_MARSHAL *cmd)
|
||||
{
|
||||
if (writex(s, cmd, 2 * sizeof(uint32_t))) {
|
||||
LOGE("failed to write marshal header\n");
|
||||
return -1;
|
||||
}
|
||||
if (writex(s, cmd->data, cmd->len)) {
|
||||
LOGE("failed to write marshal data\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user