diff options
34 files changed, 2767 insertions, 886 deletions
diff --git a/cmds/keystore/Android.mk b/cmds/keystore/Android.mk index 20f4adf20306..3daf44e40a00 100644 --- a/cmds/keystore/Android.mk +++ b/cmds/keystore/Android.mk @@ -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 := diff --git a/cmds/keystore/certtool.h b/cmds/keystore/certtool.h new file mode 100644 index 000000000000..7cd316b0fd61 --- /dev/null +++ b/cmds/keystore/certtool.h @@ -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 diff --git a/cmds/keystore/commands.c b/cmds/keystore/commands.c deleted file mode 100644 index 17dd060ac56a..000000000000 --- a/cmds/keystore/commands.c +++ /dev/null @@ -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); -} diff --git a/cmds/keystore/common.h b/cmds/keystore/common.h new file mode 100644 index 000000000000..a18114e91abc --- /dev/null +++ b/cmds/keystore/common.h @@ -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 diff --git a/cmds/keystore/keymgmt.c b/cmds/keystore/keymgmt.c new file mode 100644 index 000000000000..e4102a920e27 --- /dev/null +++ b/cmds/keystore/keymgmt.c @@ -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; +} diff --git a/cmds/keystore/keymgmt.h b/cmds/keystore/keymgmt.h new file mode 100644 index 000000000000..0f1057042bfd --- /dev/null +++ b/cmds/keystore/keymgmt.h @@ -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 diff --git a/cmds/keystore/keystore.c b/cmds/keystore/keystore.c deleted file mode 100644 index df8d83272623..000000000000 --- a/cmds/keystore/keystore.c +++ /dev/null @@ -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; -} diff --git a/cmds/keystore/keystore.h b/cmds/keystore/keystore.h deleted file mode 100644 index b9cb1851b953..000000000000 --- a/cmds/keystore/keystore.h +++ /dev/null @@ -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); diff --git a/cmds/keystore/netkeystore.c b/cmds/keystore/netkeystore.c new file mode 100644 index 000000000000..e45e24f68726 --- /dev/null +++ b/cmds/keystore/netkeystore.c @@ -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); +} + +// 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; +} diff --git a/cmds/keystore/netkeystore.h b/cmds/keystore/netkeystore.h new file mode 100644 index 000000000000..a87a667e9123 --- /dev/null +++ b/cmds/keystore/netkeystore.h @@ -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 diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6b723bc8fbbb..263f9279e69a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1085,6 +1085,27 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY"; + /** + * Activity Action: Setup wizard to launch after a platform update. This + * activity should have a string meta-data field associated with it, + * {@link #METADATA_SETUP_VERSION}, which defines the current version of + * the platform for setup. The activity will be launched only if + * {@link android.provider.Settings.Secure#LAST_SETUP_SHOWN} is not the + * same value. + * <p>Input: Nothing. + * <p>Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; + + /** + * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity + * describing the last run version of the platform that was setup. + * @hide + */ + public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2cca837b3dd6..70173336af26 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1337,6 +1337,49 @@ public final class Settings { */ public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions"; + /** + * Settings to backup. This is here so that it's in the same place as the settings + * keys and easy to update. + * @hide + */ + public static final String[] SETTINGS_TO_BACKUP = { + STAY_ON_WHILE_PLUGGED_IN, + END_BUTTON_BEHAVIOR, + WIFI_SLEEP_POLICY, + WIFI_USE_STATIC_IP, + WIFI_STATIC_IP, + WIFI_STATIC_GATEWAY, + WIFI_STATIC_NETMASK, + WIFI_STATIC_DNS1, + WIFI_STATIC_DNS2, + BLUETOOTH_DISCOVERABILITY, + BLUETOOTH_DISCOVERABILITY_TIMEOUT, + DIM_SCREEN, + SCREEN_OFF_TIMEOUT, + SCREEN_BRIGHTNESS, + VIBRATE_ON, + NOTIFICATIONS_USE_RING_VOLUME, + RINGTONE, + NOTIFICATION_SOUND, + TEXT_AUTO_REPLACE, + TEXT_AUTO_CAPS, + TEXT_AUTO_PUNCTUATE, + TEXT_SHOW_PASSWORD, + AUTO_TIME, + TIME_12_24, + DATE_FORMAT, + ACCELEROMETER_ROTATION, + DTMF_TONE_WHEN_DIALING, + DTMF_TONE_TYPE_WHEN_DIALING, + EMERGENCY_TONE, + CALL_AUTO_RETRY, + HEARING_AID, + TTY_MODE, + SOUND_EFFECTS_ENABLED, + HAPTIC_FEEDBACK_ENABLED, + SHOW_WEB_SUGGESTIONS + }; + // Settings moved to Settings.Secure /** @@ -2226,6 +2269,54 @@ public final class Settings { public static final String USE_LOCATION_FOR_SERVICES = "use_location"; /** + * Controls whether data backup is enabled. + * Type: int ( 0 = disabled, 1 = enabled ) + * @hide + */ + public static final String BACKUP_ENABLED = "backup_enabled"; + + /** + * Component of the transport to use for backup/restore. + * @hide + */ + public static final String BACKUP_TRANSPORT = "backup_transport"; + + /** + * Version for which the setup wizard was last shown. Bumped for + * each release when there is new setup information to show. + * @hide + */ + public static final String LAST_SETUP_SHOWN = "last_setup_shown"; + + /** + * @hide + */ + public static final String[] SETTINGS_TO_BACKUP = { + INSTALL_NON_MARKET_APPS, + PARENTAL_CONTROL_ENABLED, + PARENTAL_CONTROL_REDIRECT_URL, + USB_MASS_STORAGE_ENABLED, + ACCESSIBILITY_ENABLED, + ENABLED_ACCESSIBILITY_SERVICES, + TTS_USE_DEFAULTS, + TTS_DEFAULT_RATE, + TTS_DEFAULT_PITCH, + TTS_DEFAULT_SYNTH, + TTS_DEFAULT_LANG, + TTS_DEFAULT_COUNTRY, + WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + WIFI_NUM_ALLOWED_CHANNELS, + WIFI_NUM_OPEN_NETWORKS_KEPT, + BACKGROUND_DATA, + PREFERRED_NETWORK_MODE, + PREFERRED_TTY_MODE, + CDMA_CELL_BROADCAST_SMS, + PREFERRED_CDMA_SUBSCRIPTION, + ENHANCED_VOICE_PRIVACY_ENABLED + }; + + /** * Helper method for determining if a location provider is enabled. * @param cr the content resolver to use * @param provider the location provider to query diff --git a/keystore/java/android/security/CertTool.java b/keystore/java/android/security/CertTool.java new file mode 100644 index 000000000000..1dc575bccb88 --- /dev/null +++ b/keystore/java/android/security/CertTool.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +import android.content.Context; +import android.content.Intent; +import android.security.Keystore; +import android.text.TextUtils; + + +/** + * The CertTool class provides the functions to list the certs/keys, + * generate the certificate request(csr) and store the certificate into + * keystore. + * + * {@hide} + */ +public class CertTool { + public static final String ACTION_ADD_CREDENTIAL = + "android.security.ADD_CREDENTIAL"; + public static final String KEY_TYPE_NAME = "typeName"; + public static final String KEY_ITEM = "item"; + public static final String KEY_NAMESPACE = "namespace"; + public static final String KEY_DESCRIPTION = "description"; + + private static final String TAG = "CertTool"; + + private static final String TITLE_CA_CERT = "CA Certificate"; + private static final String TITLE_USER_CERT = "User Certificate"; + private static final String TITLE_PKCS12_KEYSTORE = "PKCS12 Keystore"; + private static final String TITLE_PRIVATE_KEY = "Private Key"; + private static final String UNKNOWN = "Unknown"; + private static final String ISSUER_NAME = "Issuer Name:"; + private static final String DISTINCT_NAME = "Distinct Name:"; + + private static final String CA_CERTIFICATE = "CACERT"; + private static final String USER_CERTIFICATE = "USRCERT"; + private static final String USER_KEY = "USRKEY"; + + private static final String KEYNAME_DELIMITER = "_"; + private static final Keystore keystore = Keystore.getInstance(); + + private native String generateCertificateRequest(int bits, String subject); + private native boolean isPkcs12Keystore(byte[] data); + private native int generateX509Certificate(byte[] data); + private native boolean isCaCertificate(int handle); + private native String getIssuerDN(int handle); + private native String getCertificateDN(int handle); + private native String getPrivateKeyPEM(int handle); + private native void freeX509Certificate(int handle); + + public String getUserPrivateKey(String key) { + return USER_KEY + KEYNAME_DELIMITER + key; + } + + public String getUserCertificate(String key) { + return USER_CERTIFICATE + KEYNAME_DELIMITER + key; + } + + public String getCaCertificate(String key) { + return CA_CERTIFICATE + KEYNAME_DELIMITER + key; + } + + public String[] getAllUserCertificateKeys() { + return keystore.listKeys(USER_KEY); + } + + public String[] getAllCaCertificateKeys() { + return keystore.listKeys(CA_CERTIFICATE); + } + + public String[] getSupportedKeyStrenghs() { + return new String[] {"High Grade", "Medium Grade"}; + } + + private int getKeyLength(int index) { + if (index == 0) return 2048; + return 1024; + } + + public String generateKeyPair(int keyStrengthIndex, String challenge, + String dirName) { + return generateCertificateRequest(getKeyLength(keyStrengthIndex), + dirName); + } + + private Intent prepareIntent(String title, byte[] data, String namespace, + String issuer, String distinctName) { + Intent intent = new Intent(ACTION_ADD_CREDENTIAL); + intent.putExtra(KEY_TYPE_NAME, title); + intent.putExtra(KEY_ITEM + "0", data); + intent.putExtra(KEY_NAMESPACE + "0", namespace); + intent.putExtra(KEY_DESCRIPTION + "0", ISSUER_NAME + issuer); + intent.putExtra(KEY_DESCRIPTION + "1", DISTINCT_NAME + distinctName); + return intent; + } + + private void addExtraIntentInfo(Intent intent, String namespace, + String data) { + intent.putExtra(KEY_ITEM + "1", data); + intent.putExtra(KEY_NAMESPACE + "1", namespace); + } + + public synchronized void addCertificate(byte[] data, Context context) { + int handle; + Intent intent = null; + + if (isPkcs12Keystore(data)) { + intent = prepareIntent(TITLE_PKCS12_KEYSTORE, data, USER_KEY, + UNKNOWN, UNKNOWN); + } else if ((handle = generateX509Certificate(data)) != 0) { + String issuer = getIssuerDN(handle); + String distinctName = getCertificateDN(handle); + String privateKeyPEM = getPrivateKeyPEM(handle); + if (isCaCertificate(handle)) { + intent = prepareIntent(TITLE_CA_CERT, data, CA_CERTIFICATE, + issuer, distinctName); + } else { + intent = prepareIntent(TITLE_USER_CERT, data, USER_CERTIFICATE, + issuer, distinctName); + if (!TextUtils.isEmpty(privateKeyPEM)) { + addExtraIntentInfo(intent, USER_KEY, privateKeyPEM); + } + } + freeX509Certificate(handle); + } + if (intent != null) context.startActivity(intent); + } +} diff --git a/keystore/java/android/security/Keystore.java b/keystore/java/android/security/Keystore.java index 2a3e6a7c0a21..1f14da78c2c3 100644 --- a/keystore/java/android/security/Keystore.java +++ b/keystore/java/android/security/Keystore.java @@ -20,35 +20,35 @@ package android.security; * The Keystore class provides the functions to list the certs/keys in keystore. * {@hide} */ + public abstract class Keystore { private static final String TAG = "Keystore"; private static final String[] NOTFOUND = new String[0]; + // Keystore States + public static final int BOOTUP = 0; + public static final int UNINITIALIZED = 1; + public static final int LOCKED = 2; + public static final int UNLOCKED = 3; + /** */ public static Keystore getInstance() { return new FileKeystore(); } - // for compatiblity, start from here - /** - */ - public abstract String getUserkey(String key); - - /** - */ - public abstract String getCertificate(String key); - - /** - */ - public abstract String[] getAllCertificateKeys(); - - /** - */ - public abstract String[] getAllUserkeyKeys(); - - // to here - + public abstract int lock(); + public abstract int unlock(String password); + public abstract int getState(); + public abstract int changePassword(String oldPassword, String newPassword); + public abstract int setPassword(String firstPassword); + public abstract String[] listKeys(String namespace); + public abstract int put(String namespace, String keyname, String value); + public abstract String get(String namespace, String keyname); + public abstract int remove(String namespace, String keyname); + public abstract int reset(); + + // TODO: for migrating to the mini-keystore, clean up from here /** */ public abstract String getCaCertificate(String key); @@ -89,101 +89,41 @@ public abstract class Keystore { int keyStrengthIndex, String challenge, String organizations); public abstract void addCertificate(byte[] cert); + // to here private static class FileKeystore extends Keystore { private static final String SERVICE_NAME = "keystore"; - private static final String LIST_CA_CERTIFICATES = "listcacerts"; - private static final String LIST_USER_CERTIFICATES = "listusercerts"; - private static final String GET_CA_CERTIFICATE = "getcacert"; - private static final String GET_USER_CERTIFICATE = "getusercert"; - private static final String GET_USER_KEY = "getuserkey"; - private static final String ADD_CA_CERTIFICATE = "addcacert"; - private static final String ADD_USER_CERTIFICATE = "addusercert"; - private static final String ADD_USER_KEY = "adduserkey"; - private static final String COMMAND_DELIMITER = "\t"; + private static final String CA_CERTIFICATE = "CaCertificate"; + private static final String USER_CERTIFICATE = "UserCertificate"; + private static final String USER_KEY = "UserPrivateKey"; + private static final String COMMAND_DELIMITER = " "; private static final ServiceCommand mServiceCommand = new ServiceCommand(SERVICE_NAME); - // for compatiblity, start from here - - private static final String LIST_CERTIFICATES = "listcerts"; - private static final String LIST_USERKEYS = "listuserkeys"; - private static final String PATH = "/data/misc/keystore/"; - private static final String USERKEY_PATH = PATH + "userkeys/"; - private static final String CERT_PATH = PATH + "certs/"; - - @Override - public String getUserkey(String key) { - return USERKEY_PATH + key; - } - - @Override - public String getCertificate(String key) { - return CERT_PATH + key; - } - - @Override - public String[] getAllCertificateKeys() { - try { - String result = mServiceCommand.execute(LIST_CERTIFICATES); - if (result != null) return result.split("\\s+"); - return NOTFOUND; - } catch (NumberFormatException ex) { - return NOTFOUND; - } - } - - @Override - public String[] getAllUserkeyKeys() { - try { - String result = mServiceCommand.execute(LIST_USERKEYS); - if (result != null) return result.split("\\s+"); - return NOTFOUND; - } catch (NumberFormatException ex) { - return NOTFOUND; - } - } - - // to here - + // TODO: for migrating to the mini-keystore, start from here @Override public String getUserPrivateKey(String key) { - return mServiceCommand.execute( - GET_USER_KEY + COMMAND_DELIMITER + key); + return ""; } @Override public String getUserCertificate(String key) { - return mServiceCommand.execute( - GET_USER_CERTIFICATE + COMMAND_DELIMITER + key); + return ""; } @Override public String getCaCertificate(String key) { - return mServiceCommand.execute( - GET_CA_CERTIFICATE + COMMAND_DELIMITER + key); + return ""; } @Override public String[] getAllUserCertificateKeys() { - try { - String result = mServiceCommand.execute(LIST_USER_CERTIFICATES); - if (result != null) return result.split("\\s+"); - return NOTFOUND; - } catch (NumberFormatException ex) { - return NOTFOUND; - } + return new String[0]; } @Override public String[] getAllCaCertificateKeys() { - try { - String result = mServiceCommand.execute(LIST_CA_CERTIFICATES); - if (result != null) return result.split("\\s+"); - return NOTFOUND; - } catch (NumberFormatException ex) { - return NOTFOUND; - } + return new String[0]; } @Override @@ -221,25 +161,79 @@ public abstract class Keystore { // TODO: real implementation } - private boolean addUserCertificate(String key, String certificate, - String privateKey) { - if(mServiceCommand.execute(ADD_USER_CERTIFICATE + COMMAND_DELIMITER - + key + COMMAND_DELIMITER + certificate) != null) { - if (mServiceCommand.execute(ADD_USER_KEY + COMMAND_DELIMITER - + key + COMMAND_DELIMITER + privateKey) != null) { - return true; - } - } - return false; + // to here + + @Override + public int lock() { + Reply result = mServiceCommand.execute(ServiceCommand.LOCK, null); + return (result != null) ? result.returnCode : -1; + } + + @Override + public int unlock(String password) { + Reply result = mServiceCommand.execute(ServiceCommand.UNLOCK, + password); + return (result != null) ? result.returnCode : -1; } - private boolean addCaCertificate(String key, String content) { - if (mServiceCommand.execute(ADD_CA_CERTIFICATE + COMMAND_DELIMITER - + key + COMMAND_DELIMITER + content) != null) { - return true; + @Override + public int getState() { + Reply result = mServiceCommand.execute(ServiceCommand.GET_STATE, + null); + return (result != null) ? result.returnCode : -1; + } + + @Override + public int changePassword(String oldPassword, String newPassword) { + Reply result = mServiceCommand.execute(ServiceCommand.PASSWD, + oldPassword + " " + newPassword); + return (result != null) ? result.returnCode : -1; + } + + @Override + public int setPassword(String firstPassword) { + Reply result = mServiceCommand.execute(ServiceCommand.PASSWD, + firstPassword); + return (result != null) ? result.returnCode : -1; + } + + @Override + public String[] listKeys(String namespace) { + Reply result = mServiceCommand.execute(ServiceCommand.LIST_KEYS, + namespace); + if ((result == null) || (result.returnCode != 0) || + (result.len == 0)) { + return NOTFOUND; } - return false; + return new String(result.data, 0, result.len).split("\\s+"); + } + + @Override + public int put(String namespace, String keyname, String value) { + Reply result = mServiceCommand.execute(ServiceCommand.PUT_KEY, + namespace + " " + keyname + " " + value); + return (result != null) ? result.returnCode : -1; } + @Override + public String get(String namespace, String keyname) { + Reply result = mServiceCommand.execute(ServiceCommand.GET_KEY, + namespace + " " + keyname); + return (result != null) ? ((result.returnCode != 0) ? null : + new String(result.data, 0, result.len)) : null; + } + + @Override + public int remove(String namespace, String keyname) { + Reply result = mServiceCommand.execute(ServiceCommand.REMOVE_KEY, + namespace + " " + keyname); + return (result != null) ? result.returnCode : -1; + } + + @Override + public int reset() { + Reply result = mServiceCommand.execute(ServiceCommand.RESET, null); + return (result != null) ? result.returnCode : -1; + } } } diff --git a/keystore/java/android/security/Reply.java b/keystore/java/android/security/Reply.java new file mode 100644 index 000000000000..15a0dde61e83 --- /dev/null +++ b/keystore/java/android/security/Reply.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security; + +/* + * {@hide} + */ +public class Reply { + public int len; + public int returnCode; + public byte[] data = new byte[ServiceCommand.BUFFER_LENGTH]; +} diff --git a/keystore/java/android/security/ServiceCommand.java b/keystore/java/android/security/ServiceCommand.java index f1d4302e70c8..2f335be1a085 100644 --- a/keystore/java/android/security/ServiceCommand.java +++ b/keystore/java/android/security/ServiceCommand.java @@ -35,15 +35,25 @@ public class ServiceCommand { public static final String SUCCESS = "0"; public static final String FAILED = "-1"; + // Opcodes for keystore commands. + public static final int LOCK = 0; + public static final int UNLOCK = 1; + public static final int PASSWD = 2; + public static final int GET_STATE = 3; + public static final int LIST_KEYS = 4; + public static final int GET_KEY = 5; + public static final int PUT_KEY = 6; + public static final int REMOVE_KEY = 7; + public static final int RESET = 8; + public static final int MAX_CMD_INDEX = 9; + + public static final int BUFFER_LENGTH = 4096; + private String mServiceName; private String mTag; private InputStream mIn; private OutputStream mOut; private LocalSocket mSocket; - private static final int BUFFER_LENGTH = 1024; - - private byte buf[] = new byte[BUFFER_LENGTH]; - private int buflen = 0; private boolean connect() { if (mSocket != null) { @@ -104,35 +114,47 @@ public class ServiceCommand { return false; } - private boolean readReply() { - int len, ret; - buflen = 0; + private Reply readReply() { + byte buf[] = new byte[4]; + Reply reply = new Reply(); - if (!readBytes(buf, 2)) return false; - ret = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if (ret != 0) return false; + if (!readBytes(buf, 4)) return null; + reply.len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8) | + ((((int) buf[2]) & 0xff) << 16) | + ((((int) buf[3]) & 0xff) << 24); - if (!readBytes(buf, 2)) return false; - len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if (len > BUFFER_LENGTH) { - Log.e(mTag,"invalid reply length (" + len + ")"); + if (!readBytes(buf, 4)) return null; + reply.returnCode = (((int) buf[0]) & 0xff) | + ((((int) buf[1]) & 0xff) << 8) | + ((((int) buf[2]) & 0xff) << 16) | + ((((int) buf[3]) & 0xff) << 24); + + if (reply.len > BUFFER_LENGTH) { + Log.e(mTag,"invalid reply length (" + reply.len + ")"); disconnect(); - return false; + return null; } - if (!readBytes(buf, len)) return false; - buflen = len; - return true; + if (!readBytes(reply.data, reply.len)) return null; + return reply; } - private boolean writeCommand(String _cmd) { - byte[] cmd = _cmd.getBytes(); - int len = cmd.length; - if ((len < 1) || (len > BUFFER_LENGTH)) return false; + private boolean writeCommand(int cmd, String _data) { + byte buf[] = new byte[8]; + byte[] data = _data.getBytes(); + int len = data.length; + // the length of data buf[0] = (byte) (len & 0xff); buf[1] = (byte) ((len >> 8) & 0xff); + buf[2] = (byte) ((len >> 16) & 0xff); + buf[3] = (byte) ((len >> 24) & 0xff); + // the opcode of the command + buf[4] = (byte) (cmd & 0xff); + buf[5] = (byte) ((cmd >> 8) & 0xff); + buf[6] = (byte) ((cmd >> 16) & 0xff); + buf[7] = (byte) ((cmd >> 24) & 0xff); try { - mOut.write(buf, 0, 2); - mOut.write(cmd, 0, len); + mOut.write(buf, 0, 8); + mOut.write(data, 0, len); } catch (IOException ex) { Log.e(mTag,"write error"); disconnect(); @@ -141,32 +163,28 @@ public class ServiceCommand { return true; } - private String executeCommand(String cmd) { - if (!writeCommand(cmd)) { + private Reply executeCommand(int cmd, String data) { + if (!writeCommand(cmd, data)) { /* If service died and restarted in the background * (unlikely but possible) we'll fail on the next * write (this one). Try to reconnect and write * the command one more time before giving up. */ Log.e(mTag, "write command failed? reconnect!"); - if (!connect() || !writeCommand(cmd)) { + if (!connect() || !writeCommand(cmd, data)) { return null; } } - if (readReply()) { - return new String(buf, 0, buflen); - } else { - return null; - } + return readReply(); } - public synchronized String execute(String cmd) { - String result; + public synchronized Reply execute(int cmd, String data) { + Reply result; if (!connect()) { Log.e(mTag, "connection failed"); return null; } - result = executeCommand(cmd); + result = executeCommand(cmd, data); disconnect(); return result; } diff --git a/keystore/jni/Android.mk b/keystore/jni/Android.mk new file mode 100644 index 000000000000..92c2d6d292ec --- /dev/null +++ b/keystore/jni/Android.mk @@ -0,0 +1,31 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + cert.c certtool.c + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + external/openssl/include + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libnativehelper \ + libutils \ + libcrypto + +ifeq ($(TARGET_SIMULATOR),true) +ifeq ($(TARGET_OS),linux) +ifeq ($(TARGET_ARCH),x86) +LOCAL_LDLIBS += -lpthread -ldl -lrt -lssl +endif +endif +endif + +ifeq ($(WITH_MALLOC_LEAK_CHECK),true) + LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK +endif + +LOCAL_MODULE:= libcerttool_jni + +include $(BUILD_SHARED_LIBRARY) diff --git a/keystore/jni/cert.c b/keystore/jni/cert.c new file mode 100644 index 000000000000..07f0e860cf8e --- /dev/null +++ b/keystore/jni/cert.c @@ -0,0 +1,249 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "CertTool" + +#include <stdio.h> +#include <openssl/engine.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/rsa.h> +#include <openssl/x509v3.h> +#include <cutils/log.h> + +#include "cert.h" + +static PKEY_STORE pkey_store[KEYGEN_STORE_SIZE]; +static int store_index = 0; + +static char emsg[][30] = { + "", + STR(ERR_INVALID_KEY_LENGTH), + STR(ERR_CONSTRUCT_NEW_DATA), + STR(ERR_RSA_KEYGEN), + STR(ERR_X509_PROCESS), + STR(ERR_BIO_READ), +}; + +static void save_in_store(X509_REQ *req, EVP_PKEY *pkey) +{ + EVP_PKEY *newpkey = EVP_PKEY_new(); + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_set1_RSA(newpkey, rsa); + PKEY_STORE_free(pkey_store[store_index]); + pkey_store[store_index].key_len = + i2d_X509_PUBKEY(req->req_info->pubkey, &pkey_store[store_index].public_key); + pkey_store[store_index++].pkey = newpkey; + store_index %= KEYGEN_STORE_SIZE; + RSA_free(rsa); +} + +static EVP_PKEY *get_pkey_from_store(X509 *cert) +{ + int i, key_len; + unsigned char *buf = NULL; + if ((key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &buf)) == 0) { + return NULL; + } + for (i = 0 ; i < KEYGEN_STORE_SIZE ; ++i) { + if ((key_len == pkey_store[i].key_len) && + memcmp(buf, pkey_store[i].public_key, key_len) == 0) { + break; + } + } + free(buf); + return (i == KEYGEN_STORE_SIZE) ? NULL : pkey_store[i].pkey; +} + +int gen_csr(int bits, const char *organizations, char reply[REPLY_MAX]) +{ + int len, ret_code = 0; + BIGNUM *bn = NULL; + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + X509_REQ *req = NULL; + X509_NAME *name = NULL; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) goto err; + + if ((bits != KEYLENGTH_MEDIUM) && (bits != KEYLENGTH_MAXIMUM)) { + ret_code = ERR_INVALID_KEY_LENGTH; + goto err; + } + + if (((pkey = EVP_PKEY_new()) == NULL) || + ((req = X509_REQ_new()) == NULL) || + ((rsa = RSA_new()) == NULL) || ((bn = BN_new()) == NULL)) { + ret_code = ERR_CONSTRUCT_NEW_DATA; + goto err; + } + + if (!BN_set_word(bn, RSA_F4) || + !RSA_generate_key_ex(rsa, bits, bn, NULL) || + !EVP_PKEY_assign_RSA(pkey, rsa)) { + ret_code = ERR_RSA_KEYGEN; + goto err; + } + + // rsa will be part of the req, it will be freed in X509_REQ_free(req) + rsa = NULL; + + X509_REQ_set_pubkey(req, pkey); + name = X509_REQ_get_subject_name(req); + + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, + (const unsigned char *)"US", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char *) ANDROID_KEYSTORE, + -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + (const unsigned char *)organizations, -1, -1, 0); + + if (!X509_REQ_sign(req, pkey, EVP_md5()) || + (PEM_write_bio_X509_REQ(bio, req) <= 0)) { + ret_code = ERR_X509_PROCESS; + goto err; + } + if ((len = BIO_read(bio, reply, REPLY_MAX - 1)) > 0) { + reply[len] = 0; + save_in_store(req, pkey); + } else { + ret_code = ERR_BIO_READ; + } + +err: + if (rsa) RSA_free(rsa); + if (bn) BN_free(bn); + if (req) X509_REQ_free(req); + if (pkey) EVP_PKEY_free(pkey); + if (bio) BIO_free(bio); + if ((ret_code > 0) && (ret_code < ERR_MAXIMUM)) LOGE(emsg[ret_code]); + return ret_code; +} + +int is_pkcs12(const char *buf, int bufLen) +{ + int ret = 0; + BIO *bp = NULL; + PKCS12 *p12 = NULL; + + if (!buf || bufLen < 1) goto err; + + if (buf[0] != 48) goto err; // it is not DER. + + if (!BIO_write(bp, buf, bufLen)) goto err; + + if ((p12 = d2i_PKCS12_bio(bp, NULL)) != NULL) { + PKCS12_free(p12); + ret = 1; + } +err: + if (bp) BIO_free(bp); + return ret; +} + +X509* parse_cert(const char *buf, int bufLen) +{ + X509 *cert = NULL; + BIO *bp = NULL; + + if(!buf || bufLen < 1) + return NULL; + + bp = BIO_new(BIO_s_mem()); + if (!bp) goto err; + + if (!BIO_write(bp, buf, bufLen)) goto err; + + cert = PEM_read_bio_X509(bp, NULL, NULL, NULL); + if (!cert) { + BIO_free(bp); + if((bp = BIO_new(BIO_s_mem())) == NULL) goto err; + + if(!BIO_write(bp, (char *) buf, bufLen)) goto err; + cert = d2i_X509_bio(bp, NULL); + } + +err: + if (bp) BIO_free(bp); + return cert; +} + +static int get_distinct_name(X509_NAME *dname, char *buf, int size) +{ + int i, len; + char *p, *name; + + if (X509_NAME_oneline(dname, buf, size) == NULL) { + return -1; + } + name = strstr(buf, "/CN="); + p = name = name ? (name + 4) : buf; + while (*p != 0) { + if (*p == ' ') *p = '_'; + if (*p == '/') { + *p = 0; + break; + } + ++p; + } + return 0; +} + +int get_cert_name(X509 *cert, char *buf, int size) +{ + if (!cert) return -1; + return get_distinct_name(X509_get_subject_name(cert), buf, size); +} + +int get_issuer_name(X509 *cert, char *buf, int size) +{ + if (!cert) return -1; + return get_distinct_name(X509_get_issuer_name(cert), buf, size); +} + +int is_ca_cert(X509 *cert) +{ + int ret = 0; + BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *) + X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); + if (bs != NULL) ret = bs->ca; + if (bs) BASIC_CONSTRAINTS_free(bs); + return ret; +} + +int get_private_key_pem(X509 *cert, char *buf, int size) +{ + int len = 0; + BIO *bio = NULL; + EVP_PKEY *pkey = get_pkey_from_store(cert); + + if (pkey == NULL) return -1; + + bio = BIO_new(BIO_s_mem()); + if ((bio = BIO_new(BIO_s_mem())) == NULL) goto err; + if (!PEM_write_bio_PrivateKey(bio, pkey, NULL,NULL,0,NULL, NULL)) { + goto err; + } + if ((len = BIO_read(bio, buf, size - 1)) > 0) { + buf[len] = 0; + } +err: + if (bio) BIO_free(bio); + return (len == 0) ? -1 : 0; +} diff --git a/keystore/jni/cert.h b/keystore/jni/cert.h new file mode 100644 index 000000000000..a9807b1b20f0 --- /dev/null +++ b/keystore/jni/cert.h @@ -0,0 +1,59 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef __CERT_H__ +#define __CERT_H__ + +#define ANDROID_KEYSTORE "Android Keystore" +#define KEYGEN_STORE_SIZE 5 +#define KEYLENGTH_MEDIUM 1024 +#define KEYLENGTH_MAXIMUM 2048 +#define MAX_CERT_NAME_LEN 128 +#define MAX_PEM_LENGTH 4096 +#define REPLY_MAX MAX_PEM_LENGTH + + +#define STR(token) #token +#define ERR_INVALID_KEY_LENGTH 1 +#define ERR_CONSTRUCT_NEW_DATA 2 +#define ERR_RSA_KEYGEN 3 +#define ERR_X509_PROCESS 4 +#define ERR_BIO_READ 5 +#define ERR_MAXIMUM 6 + +typedef struct { + EVP_PKEY *pkey; + unsigned char *public_key; + int key_len; +} PKEY_STORE; + +#define PKEY_STORE_free(x) { \ + if(x.pkey) EVP_PKEY_free(x.pkey); \ + if(x.public_key) free(x.public_key); \ +} + +#define nelem(x) (sizeof (x) / sizeof *(x)) + +int gen_csr(int bits, const char *organizations, char reply[REPLY_MAX]); +int is_pkcs12(const char *buf, int bufLen); +X509* parse_cert(const char *buf, int bufLen); +int get_cert_name(X509 *cert, char *buf, int size); +int get_issuer_name(X509 *cert, char *buf, int size); +int is_ca_cert(X509 *cert); +int get_private_key_pem(X509 *cert, char *buf, int size); + +#endif diff --git a/keystore/jni/certtool.c b/keystore/jni/certtool.c new file mode 100644 index 000000000000..c2a137e432ce --- /dev/null +++ b/keystore/jni/certtool.c @@ -0,0 +1,176 @@ +/* +** +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +#define LOG_TAG "CertTool" + +#include <string.h> +#include <jni.h> +#include <cutils/log.h> +#include <openssl/x509v3.h> + +#include "cert.h" + +jstring +android_security_CertTool_generateCertificateRequest(JNIEnv* env, + jobject thiz, + jint bits, + jstring subject) + +{ + char csr[REPLY_MAX]; + if (gen_csr(bits, subject, csr) == 0) { + return (*env)->NewStringUTF(env, csr); + } + return NULL; +} + +jboolean +android_security_CertTool_isPkcs12Keystore(JNIEnv* env, + jobject thiz, + jbyteArray data) +{ + char buf[REPLY_MAX]; + int len = (*env)->GetArrayLength(env, data); + + if (len > REPLY_MAX) return 0; + (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf); + return (jboolean) is_pkcs12(buf, len); +} + +jint +android_security_CertTool_generateX509Certificate(JNIEnv* env, + jobject thiz, + jbyteArray data) +{ + char buf[REPLY_MAX]; + int len = (*env)->GetArrayLength(env, data); + + if (len > REPLY_MAX) return 0; + (*env)->GetByteArrayRegion(env, data, 0, len, (jbyte*)buf); + return (jint) parse_cert(buf, len); +} + +jboolean android_security_CertTool_isCaCertificate(JNIEnv* env, + jobject thiz, + jint handle) +{ + return (handle == 0) ? (jboolean)0 : (jboolean) is_ca_cert((X509*)handle); +} + +jstring android_security_CertTool_getIssuerDN(JNIEnv* env, + jobject thiz, + jint handle) +{ + char issuer[MAX_CERT_NAME_LEN]; + + if (handle == 0) return NULL; + if (get_issuer_name((X509*)handle, issuer, MAX_CERT_NAME_LEN)) return NULL; + return (*env)->NewStringUTF(env, issuer); +} + +jstring android_security_CertTool_getCertificateDN(JNIEnv* env, + jobject thiz, + jint handle) +{ + char name[MAX_CERT_NAME_LEN]; + if (handle == 0) return NULL; + if (get_cert_name((X509*)handle, name, MAX_CERT_NAME_LEN)) return NULL; + return (*env)->NewStringUTF(env, name); +} + +jstring android_security_CertTool_getPrivateKeyPEM(JNIEnv* env, + jobject thiz, + jint handle) +{ + char pem[MAX_PEM_LENGTH]; + if (handle == 0) return NULL; + if (get_private_key_pem((X509*)handle, pem, MAX_PEM_LENGTH)) return NULL; + return (*env)->NewStringUTF(env, pem); +} + +void android_security_CertTool_freeX509Certificate(JNIEnv* env, + jobject thiz, + jint handle) +{ + if (handle != 0) X509_free((X509*)handle); +} + +/* + * Table of methods associated with the CertTool class. + */ +static JNINativeMethod gCertToolMethods[] = { + /* name, signature, funcPtr */ + {"generateCertificateRequest", "(ILjava/lang/String;)Ljava/lang/String;", + (void*)android_security_CertTool_generateCertificateRequest}, + {"isPkcs12Keystore", "(B[)I", + (void*)android_security_CertTool_isPkcs12Keystore}, + {"generateX509Certificate", "(B[)I", + (void*)android_security_CertTool_generateX509Certificate}, + {"isCaCertificate", "(I)Z", + (void*)android_security_CertTool_isCaCertificate}, + {"getIssuerDN", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_getIssuerDN}, + {"getCertificateDN", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_getCertificateDN}, + {"getPrivateKeyPEM", "(I)Ljava/lang/String;", + (void*)android_security_CertTool_getPrivateKeyPEM}, + {"freeX509Certificate", "(I)V", + (void*)android_security_CertTool_freeX509Certificate}, +}; + +/* + * Register several native methods for one class. + */ +static int registerNatives(JNIEnv* env, const char* className, + JNINativeMethod* gMethods, int numMethods) +{ + jclass clazz; + + clazz = (*env)->FindClass(env, className); + if (clazz == NULL) { + LOGE("Can not find class %s\n", className); + return JNI_FALSE; + } + + if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { + LOGE("Can not RegisterNatives\n"); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { + goto bail; + } + + if (!registerNatives(env, "android/security/CertTool", + gCertToolMethods, nelem(gCertToolMethods))) { + goto bail; + } + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + +bail: + return result; +} diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 324111bd63ab..75ca22c7e68b 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -499,7 +499,8 @@ status_t AudioFlinger::setRouting(int mode, uint32_t routes, uint32_t mask) } #ifdef WITH_A2DP - LOGD("setRouting %d %d %d, tid %d, calling tid %d\n", mode, routes, mask, gettid(), IPCThreadState::self()->getCallingPid()); + LOGV("setRouting %d %d %d, tid %d, calling tid %d\n", mode, routes, mask, gettid(), + IPCThreadState::self()->getCallingPid()); if (mode == AudioSystem::MODE_NORMAL && (mask & AudioSystem::ROUTE_BLUETOOTH_A2DP)) { AutoMutex lock(&mLock); @@ -893,7 +894,7 @@ void AudioFlinger::handleRouteDisablesA2dp_l(int routes) } LOGV("mA2dpDisableCount decremented to %d", mA2dpDisableCount); } else { - LOGE("mA2dpDisableCount is already zero"); + LOGV("mA2dpDisableCount is already zero"); } } } diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 2407d877a180..9109606baee9 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -4,6 +4,7 @@ <application android:allowClearUserData="false" android:label="@string/app_label" + android:backupAgent="SettingsBackupAgent" android:icon="@drawable/ic_launcher_settings"> <provider android:name="SettingsProvider" android:authorities="settings" diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index c28341804f42..f8adaa12e43d 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -41,4 +41,7 @@ <bool name="def_usb_mass_storage_enabled">true</bool> <bool name="def_wifi_on">false</bool> <bool name="def_networks_available_notification_on">true</bool> + + <bool name="def_backup_enabled">false</bool> + <string name="def_backup_transport"></string> </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 660b46925778..6dd117544156 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -64,7 +64,7 @@ class DatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "SettingsProvider"; private static final String DATABASE_NAME = "settings.db"; - private static final int DATABASE_VERSION = 34; + private static final int DATABASE_VERSION = 35; private Context mContext; @@ -386,6 +386,20 @@ class DatabaseHelper extends SQLiteOpenHelper { upgradeVersion = 34; } + if (upgradeVersion == 34) { + db.beginTransaction(); + try { + SQLiteStatement stmt = db.compileStatement("INSERT OR IGNORE INTO secure(name,value)" + + " VALUES(?,?);"); + loadSecure35Settings(stmt); + stmt.close(); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + upgradeVersion = 35; + } + if (upgradeVersion != currentVersion) { Log.w(TAG, "Got stuck trying to upgrade from version " + upgradeVersion + ", must wipe the settings provider"); @@ -690,9 +704,19 @@ class DatabaseHelper extends SQLiteOpenHelper { loadSetting(stmt, Settings.Secure.ALLOW_MOCK_LOCATION, "1".equals(SystemProperties.get("ro.allow.mock.location")) ? 1 : 0); + loadSecure35Settings(stmt); + stmt.close(); } + private void loadSecure35Settings(SQLiteStatement stmt) { + loadBooleanSetting(stmt, Settings.Secure.BACKUP_ENABLED, + R.bool.def_backup_enabled); + + loadStringSetting(stmt, Settings.Secure.BACKUP_TRANSPORT, + R.string.def_backup_transport); + } + private void loadSetting(SQLiteStatement stmt, String key, Object value) { stmt.bindString(1, key); stmt.bindString(2, value.toString()); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java new file mode 100644 index 000000000000..1736a4918454 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.settings; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; + +import android.backup.BackupDataInput; +import android.backup.BackupDataOutput; +import android.backup.BackupHelperAgent; +import android.bluetooth.BluetoothDevice; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.AudioManager; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +/** + * Performs backup and restore of the System and Secure settings. + * List of settings that are backed up are stored in the Settings.java file + */ +public class SettingsBackupAgent extends BackupHelperAgent { + + private static final String KEY_SYSTEM = "system"; + private static final String KEY_SECURE = "secure"; + private static final String KEY_SYNC = "sync_providers"; + + private static String[] sortedSystemKeys = null; + private static String[] sortedSecureKeys = null; + + private static final byte[] EMPTY_DATA = new byte[0]; + + private static final String TAG = "SettingsBackupAgent"; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_NAME = 1; + private static final int COLUMN_VALUE = 2; + + private static final String[] PROJECTION = { + Settings.NameValueTable._ID, + Settings.NameValueTable.NAME, + Settings.NameValueTable.VALUE + }; + + private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf"; + private static final String FILE_BT_ROOT = "/data/misc/hcid/"; + + private SettingsHelper mSettingsHelper; + + public void onCreate() { + mSettingsHelper = new SettingsHelper(this); + super.onCreate(); + } + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) throws IOException { + + byte[] systemSettingsData = getSystemSettings(); + byte[] secureSettingsData = getSecureSettings(); + byte[] syncProviders = mSettingsHelper.getSyncProviders(); + + data.writeEntityHeader(KEY_SYSTEM, systemSettingsData.length); + data.writeEntityData(systemSettingsData, systemSettingsData.length); + + data.writeEntityHeader(KEY_SECURE, secureSettingsData.length); + data.writeEntityData(secureSettingsData, secureSettingsData.length); + + data.writeEntityHeader(KEY_SYNC, syncProviders.length); + data.writeEntityData(syncProviders, syncProviders.length); + + //TODO: Permissions problem : backupFile(FILE_WIFI_SUPPLICANT, data); + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, + ParcelFileDescriptor newState) throws IOException { + + enableWifi(false); + enableBluetooth(false); + + while (data.readNextHeader()) { + final String key = data.getKey(); + if (KEY_SYSTEM.equals(key)) { + restoreSettings(data, Settings.System.CONTENT_URI); + } else if (KEY_SECURE.equals(key)) { + restoreSettings(data, Settings.Secure.CONTENT_URI); + } else if (FILE_WIFI_SUPPLICANT.equals(key)) { + restoreFile(FILE_WIFI_SUPPLICANT, data); + } else if (KEY_SYNC.equals(key)) { + mSettingsHelper.setSyncProviders(data); + } else { + data.skipEntityData(); + } + } + } + + private byte[] getSystemSettings() { + Cursor sortedCursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, + null, null, Settings.NameValueTable.NAME); + // Copy and sort the array + if (sortedSystemKeys == null) { + sortedSystemKeys = copyAndSort(Settings.System.SETTINGS_TO_BACKUP); + } + byte[] result = extractRelevantValues(sortedCursor, sortedSystemKeys); + sortedCursor.close(); + return result; + } + + private byte[] getSecureSettings() { + Cursor sortedCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, + null, null, Settings.NameValueTable.NAME); + // Copy and sort the array + if (sortedSecureKeys == null) { + sortedSecureKeys = copyAndSort(Settings.Secure.SETTINGS_TO_BACKUP); + } + byte[] result = extractRelevantValues(sortedCursor, sortedSecureKeys); + sortedCursor.close(); + return result; + } + + private void restoreSettings(BackupDataInput data, Uri contentUri) { + ContentValues cv = new ContentValues(2); + byte[] settings = new byte[data.getDataSize()]; + try { + data.readEntityData(settings, 0, settings.length); + } catch (IOException ioe) { + Log.e(TAG, "Couldn't read entity data"); + return; + } + int pos = 0; + while (pos < settings.length) { + int length = readInt(settings, pos); + pos += 4; + String settingName = length > 0? new String(settings, pos, length) : null; + pos += length; + length = readInt(settings, pos); + pos += 4; + String settingValue = length > 0? new String(settings, pos, length) : null; + pos += length; + if (!TextUtils.isEmpty(settingName) && !TextUtils.isEmpty(settingValue)) { + //Log.i(TAG, "Restore " + settingName + " = " + settingValue); + cv.clear(); + cv.put(Settings.NameValueTable.NAME, settingName); + cv.put(Settings.NameValueTable.VALUE, settingValue); + getContentResolver().insert(contentUri, cv); + mSettingsHelper.restoreValue(settingName, settingValue); + } + } + } + + private String[] copyAndSort(String[] keys) { + String[] sortedKeys = new String[keys.length]; + System.arraycopy(keys, 0, sortedKeys, 0, keys.length); + Arrays.sort(sortedKeys); + return sortedKeys; + } + + /** + * Given a cursor sorted by key name and a set of keys sorted by name, + * extract the required keys and values and write them to a byte array. + * @param sortedCursor + * @param sortedKeys + * @return + */ + byte[] extractRelevantValues(Cursor sortedCursor, String[] sortedKeys) { + byte[][] values = new byte[sortedKeys.length * 2][]; // keys and values + if (!sortedCursor.moveToFirst()) { + Log.e(TAG, "Couldn't read from the cursor"); + return new byte[0]; + } + int keyIndex = 0; + int totalSize = 0; + while (!sortedCursor.isAfterLast()) { + String name = sortedCursor.getString(COLUMN_NAME); + while (sortedKeys[keyIndex].compareTo(name.toString()) < 0) { + keyIndex++; + if (keyIndex == sortedKeys.length) break; + } + if (keyIndex < sortedKeys.length && name.equals(sortedKeys[keyIndex])) { + String value = sortedCursor.getString(COLUMN_VALUE); + byte[] nameBytes = name.toString().getBytes(); + totalSize += 4 + nameBytes.length; + values[keyIndex * 2] = nameBytes; + byte[] valueBytes; + if (TextUtils.isEmpty(value)) { + valueBytes = null; + totalSize += 4; + } else { + valueBytes = value.toString().getBytes(); + totalSize += 4 + valueBytes.length; + //Log.i(TAG, "Backing up " + name + " = " + value); + } + values[keyIndex * 2 + 1] = valueBytes; + keyIndex++; + } + if (keyIndex == sortedKeys.length || !sortedCursor.moveToNext()) { + break; + } + } + + byte[] result = new byte[totalSize]; + int pos = 0; + for (int i = 0; i < sortedKeys.length * 2; i++) { + if (values[i] != null) { + pos = writeInt(result, pos, values[i].length); + pos = writeBytes(result, pos, values[i]); + } + } + return result; + } + + private void backupFile(String filename, BackupDataOutput data) { + try { + File file = new File(filename); + if (file.exists()) { + byte[] bytes = new byte[(int) file.length()]; + FileInputStream fis = new FileInputStream(file); + int offset = 0; + int got = 0; + do { + got = fis.read(bytes, offset, bytes.length - offset); + if (got > 0) offset += got; + } while (offset < bytes.length && got > 0); + data.writeEntityHeader(filename, bytes.length); + data.writeEntityData(bytes, bytes.length); + } else { + data.writeEntityHeader(filename, 0); + data.writeEntityData(EMPTY_DATA, 0); + } + } catch (IOException ioe) { + Log.w(TAG, "Couldn't backup " + filename); + } + } + + private void restoreFile(String filename, BackupDataInput data) { + byte[] bytes = new byte[data.getDataSize()]; + if (bytes.length <= 0) return; + try { + data.readEntityData(bytes, 0, bytes.length); + FileOutputStream fos = new FileOutputStream(filename); + fos.write(bytes); + } catch (IOException ioe) { + Log.w(TAG, "Couldn't restore " + filename); + } + } + + /** + * Write an int in BigEndian into the byte array. + * @param out byte array + * @param pos current pos in array + * @param value integer to write + * @return the index after adding the size of an int (4) + */ + private int writeInt(byte[] out, int pos, int value) { + out[pos + 0] = (byte) ((value >> 24) & 0xFF); + out[pos + 1] = (byte) ((value >> 16) & 0xFF); + out[pos + 2] = (byte) ((value >> 8) & 0xFF); + out[pos + 3] = (byte) ((value >> 0) & 0xFF); + return pos + 4; + } + + private int writeBytes(byte[] out, int pos, byte[] value) { + System.arraycopy(value, 0, out, pos, value.length); + return pos + value.length; + } + + private int readInt(byte[] in, int pos) { + int result = + ((in[pos ] & 0xFF) << 24) | + ((in[pos + 1] & 0xFF) << 16) | + ((in[pos + 2] & 0xFF) << 8) | + ((in[pos + 3] & 0xFF) << 0); + return result; + } + + private void enableWifi(boolean enable) { + WifiManager wfm = (WifiManager) getSystemService(Context.WIFI_SERVICE); + if (wfm != null) { + wfm.setWifiEnabled(enable); + } + } + + private void enableBluetooth(boolean enable) { + BluetoothDevice bt = (BluetoothDevice) getSystemService(Context.BLUETOOTH_SERVICE); + if (bt != null) { + if (!enable) { + bt.disable(); + } else { + bt.enable(); + } + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java new file mode 100644 index 000000000000..b0655872df29 --- /dev/null +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.settings; + +import android.backup.BackupDataInput; +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioManager; +import android.os.IHardwareService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.content.IContentService; +import android.util.Log; + +public class SettingsHelper { + private static final String TAG = "SettingsHelper"; + + private Context mContext; + private AudioManager mAudioManager; + private IContentService mContentService; + private static final String SYNC_AUTO = "auto_sync"; + private static final String SYNC_MAIL = "gmail-ls_sync"; + private static final String SYNC_CALENDAR = "calendar_sync"; + private static final String SYNC_CONTACTS = "contacts_sync"; + + public SettingsHelper(Context context) { + mContext = context; + mAudioManager = (AudioManager) context + .getSystemService(Context.AUDIO_SERVICE); + mContentService = ContentResolver.getContentService(); + } + + public void restoreValue(String name, String value) { + if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) { + setBrightness(Integer.parseInt(value)); + } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) { + if (Integer.parseInt(value) == 1) { + mAudioManager.loadSoundEffects(); + } else { + mAudioManager.unloadSoundEffects(); + } + } + } + + private void setBrightness(int brightness) { + try { + IHardwareService hardware = IHardwareService.Stub + .asInterface(ServiceManager.getService("hardware")); + if (hardware != null) { + hardware.setBacklights(brightness); + } + } catch (RemoteException doe) { + + } + } + + static final String[] PROVIDERS = { "gmail-ls", "calendar", "contacts" }; + + byte[] getSyncProviders() { + byte[] sync = new byte[1 + PROVIDERS.length]; + try { + sync[0] = (byte) (mContentService.getListenForNetworkTickles() ? 1 : 0); + for (int i = 0; i < PROVIDERS.length; i++) { + sync[i + 1] = (byte) + (mContentService.getSyncProviderAutomatically(PROVIDERS[i]) ? 1 : 0); + } + } catch (RemoteException re) { + Log.w(TAG, "Unable to backup sync providers"); + return sync; + } + return sync; + } + + void setSyncProviders(BackupDataInput backup) { + byte[] sync = new byte[backup.getDataSize()]; + + try { + backup.readEntityData(sync, 0, sync.length); + mContentService.setListenForNetworkTickles(sync[0] == 1); + for (int i = 0; i < PROVIDERS.length; i++) { + mContentService.setSyncProviderAutomatically(PROVIDERS[i], sync[i + 1] > 0); + } + } catch (RemoteException re) { + Log.w(TAG, "Unable to restore sync providers"); + } catch (java.io.IOException ioe) { + Log.w(TAG, "Unable to read sync settings"); + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 3db52ebd00bf..a21bf320e7a1 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -16,6 +16,9 @@ package com.android.providers.settings; +import java.io.FileNotFoundException; + +import android.backup.IBackupManager; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; @@ -27,6 +30,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.media.RingtoneManager; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.DrmStore; import android.provider.MediaStore; @@ -34,8 +38,6 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; -import java.io.FileNotFoundException; - public class SettingsProvider extends ContentProvider { private static final String TAG = "SettingsProvider"; private static final boolean LOCAL_LOGV = false; @@ -137,6 +139,17 @@ public class SettingsProvider extends ContentProvider { SystemProperties.set(property, Long.toString(version)); } + // Inform the backup manager about a data change + IBackupManager ibm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (ibm != null) { + try { + ibm.dataChanged(getContext().getPackageName()); + } catch (Exception e) { + // Try again later + } + } + // Now send the notification through the content framework. String notify = uri.getQueryParameter("notify"); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index b15c06ba2804..953e4012ee76 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -74,10 +74,6 @@ class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; - // Secure settings - private static final String BACKUP_TRANSPORT_SETTING = "backup_transport"; - private static final String BACKUP_ENABLED_SETTING = "backup_enabled"; - // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = 60 * 60 * 1000; @@ -165,10 +161,8 @@ class BackupManagerService extends IBackupManager.Stub { mActivityManager = ActivityManagerNative.getDefault(); // Set up our bookkeeping - // !!! STOPSHIP: make this disabled by default so that we then gate on - // setupwizard or other opt-out UI - mEnabled = (Settings.Secure.getInt(mContext.getContentResolver(), - BACKUP_ENABLED_SETTING, 1) != 0); + mEnabled = Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.BACKUP_ENABLED, 0) != 0; mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); mDataDir = Environment.getDownloadCacheDirectory(); @@ -192,13 +186,10 @@ class BackupManagerService extends IBackupManager.Stub { registerTransport(localName.flattenToShortString(), mLocalTransport); mGoogleTransport = null; - // !!! TODO: set up the default transport name "the right way" - mCurrentTransport = Settings.Secure.getString(mContext.getContentResolver(), - BACKUP_TRANSPORT_SETTING); - if (mCurrentTransport == null) { - mCurrentTransport = "com.google.android.backup/.BackupTransportService"; - Settings.Secure.putString(mContext.getContentResolver(), - BACKUP_TRANSPORT_SETTING, mCurrentTransport); + mCurrentTransport = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.BACKUP_TRANSPORT); + if ("".equals(mCurrentTransport)) { + mCurrentTransport = null; } if (DEBUG) Log.v(TAG, "Starting with transport " + mCurrentTransport); @@ -391,6 +382,7 @@ class BackupManagerService extends IBackupManager.Stub { case MSG_RUN_RESTORE: { RestoreParams params = (RestoreParams)msg.obj; + Log.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); (new PerformRestoreThread(params.transport, params.observer, params.token)).start(); break; } @@ -837,6 +829,7 @@ class BackupManagerService extends IBackupManager.Stub { PerformRestoreThread(IBackupTransport transport, IRestoreObserver observer, long restoreSetToken) { mTransport = transport; + Log.d(TAG, "PerformRestoreThread mObserver=" + mObserver); mObserver = observer; mToken = restoreSetToken; @@ -850,7 +843,8 @@ class BackupManagerService extends IBackupManager.Stub { @Override public void run() { - if (DEBUG) Log.v(TAG, "Beginning restore process"); + if (DEBUG) Log.v(TAG, "Beginning restore process mTransport=" + mTransport + + " mObserver=" + mObserver + " mToken=" + mToken); /** * Restore sequence: * @@ -1011,6 +1005,8 @@ class BackupManagerService extends IBackupManager.Stub { Log.e(TAG, "Error finishing restore", e); } + Log.d(TAG, "finishing restore mObserver=" + mObserver); + if (mObserver != null) { try { mObserver.restoreFinished(error); @@ -1026,6 +1022,8 @@ class BackupManagerService extends IBackupManager.Stub { // !!! TODO: actually run the restore through mTransport final String packageName = app.packageName; + Log.d(TAG, "processOneRestore packageName=" + packageName); + // !!! TODO: get the dirs from the transport File backupDataName = new File(mDataDir, packageName + ".restore"); backupDataName.delete(); @@ -1086,7 +1084,7 @@ class BackupManagerService extends IBackupManager.Stub { // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. HashSet<ApplicationInfo> targets; - if ((mContext.checkPermission("android.permission.BACKUP", Binder.getCallingPid(), + if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { targets = mBackupParticipants.get(Binder.getCallingUid()); } else { @@ -1147,7 +1145,7 @@ class BackupManagerService extends IBackupManager.Stub { // Run a backup pass immediately for any applications that have declared // that they have pending updates. public void backupNow() throws RemoteException { - mContext.enforceCallingPermission("android.permission.BACKUP", "backupNow"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "backupNow"); if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { @@ -1157,12 +1155,12 @@ class BackupManagerService extends IBackupManager.Stub { // Enable/disable the backup transport public void setBackupEnabled(boolean enable) { - mContext.enforceCallingPermission("android.permission.BACKUP", "setBackupEnabled"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); boolean wasEnabled = mEnabled; synchronized (this) { - Settings.Secure.putInt(mContext.getContentResolver(), BACKUP_ENABLED_SETTING, - enable ? 1 : 0); + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BACKUP_ENABLED, enable ? 1 : 0); mEnabled = enable; } @@ -1179,20 +1177,20 @@ class BackupManagerService extends IBackupManager.Stub { // Report whether the backup mechanism is currently enabled public boolean isBackupEnabled() { - mContext.enforceCallingPermission("android.permission.BACKUP", "isBackupEnabled"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); return mEnabled; // no need to synchronize just to read it } // Report the name of the currently active transport public String getCurrentTransport() { mContext.enforceCallingPermission("android.permission.BACKUP", "getCurrentTransport"); - Log.v(TAG, "getCurrentTransport() returning " + mCurrentTransport); + Log.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport); return mCurrentTransport; } // Report all known, available backup transports public String[] listAllTransports() { - mContext.enforceCallingPermission("android.permission.BACKUP", "listAllTransports"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "listAllTransports"); String[] list = null; ArrayList<String> known = new ArrayList<String>(); @@ -1213,15 +1211,15 @@ class BackupManagerService extends IBackupManager.Stub { // name is not one of the available transports, no action is taken and the method // returns null. public String selectBackupTransport(String transport) { - mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "selectBackupTransport"); synchronized (mTransports) { String prevTransport = null; if (mTransports.get(transport) != null) { prevTransport = mCurrentTransport; mCurrentTransport = transport; - Settings.Secure.putString(mContext.getContentResolver(), BACKUP_TRANSPORT_SETTING, - transport); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.BACKUP_TRANSPORT, transport); Log.v(TAG, "selectBackupTransport() set " + mCurrentTransport + " returning " + prevTransport); } else { @@ -1267,7 +1265,7 @@ class BackupManagerService extends IBackupManager.Stub { // Hand off a restore session public IRestoreSession beginRestoreSession(String transport) { - mContext.enforceCallingPermission("android.permission.BACKUP", "beginRestoreSession"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "beginRestoreSession"); synchronized(this) { if (mActiveRestoreSession != null) { @@ -1293,7 +1291,7 @@ class BackupManagerService extends IBackupManager.Stub { // --- Binder interface --- public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { - mContext.enforceCallingPermission("android.permission.BACKUP", + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "getAvailableRestoreSets"); try { @@ -1312,7 +1310,9 @@ class BackupManagerService extends IBackupManager.Stub { public int performRestore(long token, IRestoreObserver observer) throws android.os.RemoteException { - mContext.enforceCallingPermission("android.permission.BACKUP", "performRestore"); + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "performRestore"); + + Log.d(TAG, "performRestore token=" + token + " observer=" + observer); if (mRestoreSets != null) { for (int i = 0; i < mRestoreSets.length; i++) { @@ -1330,9 +1330,11 @@ class BackupManagerService extends IBackupManager.Stub { } public void endRestoreSession() throws android.os.RemoteException { - mContext.enforceCallingPermission("android.permission.BACKUP", + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "endRestoreSession"); + Log.d(TAG, "endRestoreSession"); + mRestoreTransport.finishRestore(); mRestoreTransport = null; synchronized(BackupManagerService.this) { diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java index 3fc1e0ea5879..9b0a2d465dbe 100644 --- a/services/java/com/android/server/HeadsetObserver.java +++ b/services/java/com/android/server/HeadsetObserver.java @@ -35,6 +35,7 @@ import java.io.FileNotFoundException; */ class HeadsetObserver extends UEventObserver { private static final String TAG = HeadsetObserver.class.getSimpleName(); + private static final boolean LOG = false; private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w"; private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state"; @@ -61,7 +62,7 @@ class HeadsetObserver extends UEventObserver { @Override public void onUEvent(UEventObserver.UEvent event) { - Log.v(TAG, "Headset UEVENT: " + event.toString()); + if (LOG) Log.v(TAG, "Headset UEVENT: " + event.toString()); try { update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE"))); diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index cfa625c59448..2dd70efcec1b 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -2325,6 +2325,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo if (!mDisplayFrozen) { if (mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { mNextAppTransition = transit; + } else if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) { + // Opening a new task always supersedes a close for the anim. + mNextAppTransition = transit; + } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) { + // Opening a new activity always supersedes a close for the anim. + mNextAppTransition = transit; } mAppTransitionReady = false; mAppTransitionTimeout = false; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index f716571e5577..2fe4dd4c3109 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -745,6 +745,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int mFactoryTest; + boolean mCheckedForSetup; + /** * The time at which we will allow normal application switches again, * after a call to {@link #stopAppSwitches()}. @@ -1774,6 +1776,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.stopped = true; } + // Launch the new version setup screen if needed. We do this -after- + // launching the initial activity (that is, home), so that it can have + // a chance to initialize itself while in the background, making the + // switch back to it faster and look better. + startSetupActivityLocked(); + return true; } @@ -2355,6 +2363,96 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + private boolean startHomeActivityLocked() { + if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL + && mTopAction == null) { + // We are running in factory test mode, but unable to find + // the factory test app, so just sit around displaying the + // error message and don't try to start anything. + return false; + } + Intent intent = new Intent( + mTopAction, + mTopData != null ? Uri.parse(mTopData) : null); + intent.setComponent(mTopComponent); + if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + intent.addCategory(Intent.CATEGORY_HOME); + } + ActivityInfo aInfo = + intent.resolveActivityInfo(mContext.getPackageManager(), + STOCK_PM_FLAGS); + if (aInfo != null) { + intent.setComponent(new ComponentName( + aInfo.applicationInfo.packageName, aInfo.name)); + // Don't do this if the home app is currently being + // instrumented. + ProcessRecord app = getProcessRecordLocked(aInfo.processName, + aInfo.applicationInfo.uid); + if (app == null || app.instrumentationClass == null) { + intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityLocked(null, intent, null, null, 0, aInfo, + null, null, 0, 0, 0, false, false); + } + } + + + return true; + } + + /** + * Starts the "new version setup screen" if appropriate. + */ + private void startSetupActivityLocked() { + // Only do this once per boot. + if (mCheckedForSetup) { + return; + } + + // We will show this screen if the current one is a different + // version than the last one shown, and we are not running in + // low-level factory test mode. + final ContentResolver resolver = mContext.getContentResolver(); + if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL && + Settings.Secure.getInt(resolver, + Settings.Secure.DEVICE_PROVISIONED, 0) != 0) { + mCheckedForSetup = true; + + // See if we should be showing the platform update setup UI. + Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP); + List<ResolveInfo> ris = mSelf.mContext.getPackageManager() + .queryIntentActivities(intent, PackageManager.GET_META_DATA); + + // We don't allow third party apps to replace this. + ResolveInfo ri = null; + for (int i=0; ris != null && i<ris.size(); i++) { + if ((ris.get(i).activityInfo.applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) != 0) { + ri = ris.get(i); + break; + } + } + + if (ri != null) { + String vers = ri.activityInfo.metaData != null + ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION) + : null; + if (vers == null && ri.activityInfo.applicationInfo.metaData != null) { + vers = ri.activityInfo.applicationInfo.metaData.getString( + Intent.METADATA_SETUP_VERSION); + } + String lastVers = Settings.Secure.getString( + resolver, Settings.Secure.LAST_SETUP_SHOWN); + if (vers != null && !vers.equals(lastVers)) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setComponent(new ComponentName( + ri.activityInfo.packageName, ri.activityInfo.name)); + startActivityLocked(null, intent, null, null, 0, ri.activityInfo, + null, null, 0, 0, 0, false, false); + } + } + } + } + /** * Ensure that the top activity in the stack is resumed. * @@ -2376,37 +2474,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (next == null) { // There are no more activities! Let's just start up the // Launcher... - if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL - && mTopAction == null) { - // We are running in factory test mode, but unable to find - // the factory test app, so just sit around displaying the - // error message and don't try to start anything. - return false; - } - Intent intent = new Intent( - mTopAction, - mTopData != null ? Uri.parse(mTopData) : null); - intent.setComponent(mTopComponent); - if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - intent.addCategory(Intent.CATEGORY_HOME); - } - ActivityInfo aInfo = - intent.resolveActivityInfo(mContext.getPackageManager(), - STOCK_PM_FLAGS); - if (aInfo != null) { - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - // Don't do this if the home app is currently being - // instrumented. - ProcessRecord app = getProcessRecordLocked(aInfo.processName, - aInfo.applicationInfo.uid); - if (app == null || app.instrumentationClass == null) { - intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false); - } - } - return true; + return startHomeActivityLocked(); } next.delayedResume = false; diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java index f623080a2350..0991e8ce2240 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java @@ -175,6 +175,7 @@ public class DatabaseGeneralTest extends TestCase implements PerformanceTestCase assertEquals("+" + PHONE_NUMBER, number); c.close(); + /* c = mDatabase.query("phones", null, "PHONE_NUMBERS_EQUAL(num, '5551212')", null, null, null, null); assertNotNull(c); @@ -183,6 +184,7 @@ public class DatabaseGeneralTest extends TestCase implements PerformanceTestCase number = c.getString(c.getColumnIndexOrThrow("num")); assertEquals("+" + PHONE_NUMBER, number); c.close(); + */ c = mDatabase.query("phones", null, "PHONE_NUMBERS_EQUAL(num, '011" + PHONE_NUMBER + "')", null, null, null, null); @@ -203,85 +205,97 @@ public class DatabaseGeneralTest extends TestCase implements PerformanceTestCase c.close(); } + + private void phoneNumberCompare(String phone1, String phone2, boolean equal) + throws Exception { + String[] temporalPhoneNumbers = new String[2]; + temporalPhoneNumbers[0] = phone1; + temporalPhoneNumbers[1] = phone2; + + Cursor cursor = mDatabase.rawQuery( + "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", + temporalPhoneNumbers); + try { + assertNotNull(cursor); + assertTrue(cursor.moveToFirst()); + if (equal) { + assertEquals(String.format("Unexpectedly, \"%s != %s\".", phone1, phone2), + "equal", cursor.getString(0)); + } else { + assertEquals(String.format("Unexpectedly, \"%s\" == \"%s\".", phone1, phone2), + "not equal", cursor.getString(0)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + private void assertPhoneNumberEqual(String phone1, String phone2) throws Exception { + phoneNumberCompare(phone1, phone2, true); + } + + private void assertPhoneNumberNotEqual(String phone1, String phone2) throws Exception { + phoneNumberCompare(phone1, phone2, false); + } + /** * Tests international matching issues for the PHONE_NUMBERS_EQUAL function. * * @throws Exception */ + @SmallTest public void testPhoneNumbersEqualInternationl() throws Exception { - Cursor c; - String[] phoneNumbers = new String[2]; + assertPhoneNumberEqual("1", "1"); + assertPhoneNumberEqual("123123", "123123"); + assertPhoneNumberNotEqual("123123", "923123"); + assertPhoneNumberNotEqual("123123", "123129"); + assertPhoneNumberNotEqual("123123", "1231234"); + assertPhoneNumberNotEqual("123123", "0123123"); + assertPhoneNumberEqual("650-253-0000", "6502530000"); + assertPhoneNumberEqual("650-253-0000", "650 253 0000"); + assertPhoneNumberEqual("650 253 0000", "6502530000"); + assertPhoneNumberEqual("+1 650-253-0000", "6502530000"); + assertPhoneNumberEqual("001 650-253-0000", "6502530000"); + assertPhoneNumberEqual("0111 650-253-0000", "6502530000"); // Russian trunk digit - phoneNumbers[0] = "+79161234567"; // globablly dialable number - phoneNumbers[1] = "89161234567"; // in-country dialable number - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("equal", c.getString(0)); - c.close(); + assertPhoneNumberEqual("+79161234567", "89161234567"); // French trunk digit - phoneNumbers[0] = "+33123456789"; // globablly dialable number - phoneNumbers[1] = "0123456789"; // in-country dialable number - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("equal", c.getString(0)); - c.close(); - + assertPhoneNumberEqual("+33123456789", "0123456789"); // Trunk digit for city codes in the Netherlands - phoneNumbers[0] = "+31771234567"; // globablly dialable number - phoneNumbers[1] = "0771234567"; // in-country dialable number - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("equal", c.getString(0)); - c.close(); + assertPhoneNumberEqual("+31771234567", "0771234567"); // Test broken caller ID seen on call from Thailand to the US - phoneNumbers[0] = "+66811234567"; // in address book - phoneNumbers[1] = "166811234567"; // came in from the network - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("equal", c.getString(0)); - c.close(); + assertPhoneNumberEqual("+66811234567", "166811234567"); // Test the same in-country number with different country codes - phoneNumbers[0] = "+33123456789"; - phoneNumbers[1] = "+1123456789"; - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("not equal", c.getString(0)); - c.close(); + assertPhoneNumberNotEqual("+33123456789", "+1123456789"); // Test one number with country code and the other without - phoneNumbers[0] = "5125551212"; - phoneNumbers[1] = "+15125551212"; - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("equal", c.getString(0)); - c.close(); + assertPhoneNumberEqual("5125551212", "+15125551212"); // Test two NANP numbers that only differ in the area code - phoneNumbers[0] = "5125551212"; - phoneNumbers[1] = "6505551212"; - c = mDatabase.rawQuery( - "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?) THEN 'equal' ELSE 'not equal' END", - phoneNumbers); - assertTrue(c.moveToFirst()); - assertEquals("not equal", c.getString(0)); - c.close(); + assertPhoneNumberNotEqual("5125551212", "6505551212"); + + // Japanese phone numbers + assertPhoneNumberEqual("090-1234-5678", "+819012345678"); + assertPhoneNumberEqual("090(1234)5678", "+819012345678"); + assertPhoneNumberEqual("090-1234-5678", "+81-90-1234-5678"); + + // Equador + assertPhoneNumberEqual("+593(800)123-1234", "8001231234"); + assertPhoneNumberEqual("+593-2-1234-123", "21234123"); + + // Two continuous 0 at the beginning of the phone string should not be + // treated as trunk prefix. + assertPhoneNumberNotEqual("008001231234", "8001231234"); + + // Confirm that the bug found before does not re-appear. + assertPhoneNumberNotEqual("080-1234-5678", "+819012345678"); } @MediumTest diff --git a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java index 51e841cb8d3a..7720041fccca 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/TextUtilsTest.java @@ -276,6 +276,7 @@ public class TextUtilsTest extends TestCase { Spannable s3 = new SpannableString(s1); s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); TextPaint p = new TextPaint(); + p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); for (int i = 0; i < 100; i++) { for (int j = 0; j < 3; j++) { diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 5b8ced67662c..7a15f273d140 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -253,6 +253,15 @@ public class WifiManager { IWifiManager mService; Handler mHandler; + /* Maximum number of active locks we allow. + * This limit was added to prevent apps from creating a ridiculous number + * of locks and crashing the system by overflowing the global ref table. + */ + private static final int MAX_ACTIVE_LOCKS = 50; + + /* Number of currently active WifiLocks and MulticastLocks */ + private int mActiveLockCount; + /** * Create a new WifiManager instance. * Applications will almost always want to use @@ -702,6 +711,14 @@ public class WifiManager { if (mRefCounted ? (++mRefCount > 0) : (!mHeld)) { try { mService.acquireWifiLock(mBinder, mLockType, mTag); + synchronized (WifiManager.this) { + if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { + mService.releaseWifiLock(mBinder); + throw new UnsupportedOperationException( + "Exceeded maximum number of wifi locks"); + } + mActiveLockCount++; + } } catch (RemoteException ignore) { } mHeld = true; @@ -726,6 +743,9 @@ public class WifiManager { if (mRefCounted ? (--mRefCount == 0) : (mHeld)) { try { mService.releaseWifiLock(mBinder); + synchronized (WifiManager.this) { + mActiveLockCount--; + } } catch (RemoteException ignore) { } mHeld = false; @@ -783,6 +803,9 @@ public class WifiManager { if (mHeld) { try { mService.releaseWifiLock(mBinder); + synchronized (WifiManager.this) { + mActiveLockCount--; + } } catch (RemoteException ignore) { } } @@ -877,6 +900,14 @@ public class WifiManager { if (!mHeld) { try { mService.acquireMulticastLock(mBinder, mTag); + synchronized (WifiManager.this) { + if (mActiveLockCount >= MAX_ACTIVE_LOCKS) { + mService.releaseMulticastLock(); + throw new UnsupportedOperationException( + "Exceeded maximum number of wifi locks"); + } + mActiveLockCount++; + } mHeld = true; } catch (RemoteException ignore) { } @@ -901,6 +932,9 @@ public class WifiManager { if (mHeld) { try { mService.releaseMulticastLock(); + synchronized (WifiManager.this) { + mActiveLockCount--; + } mHeld = false; } catch (RemoteException ignore) { } |