Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c186c66a authored by Chung-yih Wang's avatar Chung-yih Wang
Browse files

Implement the generic mini-keystore for security.

1. We will progressively migrate to this implementation.
2. For richc to have a quick review on the keymgmt part.
3. Add remove_key and make sure all functions are working.
4. Add permission check for get operation.
5. Return the retry count if unlock failed.
6. Add the reset operation for keystore reset.
7. Add the putfile shell command for putting the key value from file.
8. Fix the boundary issue during parsing command.
9. Use the ' ' as delimiter and reset the reply structure for each request.
10. Add change password retry count check.
11. Extract the read_marshal/write_marshal for certtool.h.
12. Remove the old implementation.
parent ce6d1721
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
**
** 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

cmds/keystore/commands.c

deleted100644 → 0
+0 −227
Original line number Diff line number Diff line
/*
** 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);
}

cmds/keystore/common.h

0 → 100644
+60 −0
Original line number Diff line number Diff line
/*
**
** 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 −0
Original line number Diff line number Diff line
/*
** 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;
}
Loading