ccf407f7ac
+ some boundary checks.
427 lines
11 KiB
C
427 lines
11 KiB
C
/*
|
|
** 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];
|
|
|
|
if ((keyfile == NULL) || (strlen(keyfile) >= (KEYFILE_LEN - 4))) {
|
|
LOGE("keyfile name is too long or null");
|
|
return -1;
|
|
}
|
|
strcpy(tmpfile, keyfile);
|
|
strcat(tmpfile, ".tmp");
|
|
|
|
// prepare the blob
|
|
if (IV_LEN > USER_KEY_LEN) {
|
|
LOGE("iv length is too long.");
|
|
return -1;
|
|
}
|
|
memcpy(blob->iv, iv, IV_LEN);
|
|
blob->blob_size = get_blob_size(blob);
|
|
if (blob->blob_size > MAX_BLOB_LEN) {
|
|
LOGE("blob data size is too large.");
|
|
return -1;
|
|
}
|
|
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
|
|
if (strlen(MASTER_KEY_TAG) >= USER_KEY_LEN) return -1;
|
|
strlcpy(blob.keyname, MASTER_KEY_TAG, USER_KEY_LEN);
|
|
blob.value_size = USER_KEY_LEN;
|
|
if (USER_KEY_LEN > MAX_KEY_VALUE_LENGTH) {
|
|
LOGE("master_key length is too long.");
|
|
return -1;
|
|
}
|
|
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 (blob.value_size > USER_KEY_LEN) {
|
|
LOGE("the blob's value size is too large");
|
|
return -1;
|
|
}
|
|
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 (strlen(new_pass) < MIN_PASSWD_LENGTH) 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;
|
|
if ((strlen(namespace) >= MAX_KEY_NAME_LENGTH) ||
|
|
(strlen(keyname) >= MAX_KEY_NAME_LENGTH)) {
|
|
LOGE("keyname is too long.");
|
|
return -1;
|
|
}
|
|
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;
|
|
}
|
|
if ((strlen(namespace) >= MAX_KEY_NAME_LENGTH) ||
|
|
(strlen(keyname) >= MAX_KEY_NAME_LENGTH)) {
|
|
LOGE("keyname is too long.");
|
|
return -1;
|
|
}
|
|
sprintf(keyfile, KEYFILE_NAME, namespace, keyname);
|
|
strcpy(blob.keyname, keyname);
|
|
blob.value_size = size;
|
|
if (size > MAX_KEY_VALUE_LENGTH) {
|
|
LOGE("the data size is too large.");
|
|
return -1;
|
|
}
|
|
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;
|
|
}
|
|
if ((strlen(namespace) >= MAX_KEY_NAME_LENGTH) ||
|
|
(strlen(keyname) >= MAX_KEY_NAME_LENGTH)) {
|
|
LOGE("keyname is too long.");
|
|
return -1;
|
|
}
|
|
sprintf(keyfile, KEYFILE_NAME, namespace, keyname);
|
|
ret = load_n_decrypt(keyname, keyfile, &decryptKey, &blob);
|
|
if (!ret) {
|
|
if ((blob.value_size > MAX_KEY_VALUE_LENGTH)) {
|
|
LOGE("blob value size is too large.");
|
|
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 (state != UNLOCKED) {
|
|
LOGE("Can not list key with current state %d\n", state);
|
|
return -1;
|
|
}
|
|
|
|
if (!namespace || ((d = opendir("."))) == NULL) {
|
|
LOGE("cannot open keystore dir or namespace is null\n");
|
|
return -1;
|
|
}
|
|
|
|
if (strlen(namespace) >= MAX_KEY_NAME_LENGTH) {
|
|
LOGE("namespace is too long.");
|
|
return -1;
|
|
}
|
|
|
|
reply[0] = 0;
|
|
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;
|
|
if (strlen(data) < MIN_PASSWD_LENGTH) 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()
|
|
{
|
|
int ret = 0;
|
|
DIR *d;
|
|
struct dirent *de;
|
|
|
|
if ((d = opendir(".")) == NULL) {
|
|
LOGE("cannot open keystore dir\n");
|
|
return -1;
|
|
}
|
|
while ((de = readdir(d))) {
|
|
if (unlink(de->d_name) != 0) ret = -1;
|
|
}
|
|
closedir(d);
|
|
state = UNINITIALIZED;
|
|
if (ret == 0) {
|
|
LOGI("keystore is reset.");
|
|
} else {
|
|
LOGI("keystore can not be cleaned up entirely.");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
}
|