/* ** 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; }