replicant-frameworks_native/cmds/keystore/netkeystore.c
Chung-yih Wang c186c66a29 Implement the generic mini-keystore for security.
1. We will progressively migrate to this implementation.
2. For richc to have a quick review on the keymgmt part.
3. Add remove_key and make sure all functions are working.
4. Add permission check for get operation.
5. Return the retry count if unlock failed.
6. Add the reset operation for keystore reset.
7. Add the putfile shell command for putting the key value from file.
8. Fix the boundary issue during parsing command.
9. Use the ' ' as delimiter and reset the reply structure for each request.
10. Add change password retry count check.
11. Extract the read_marshal/write_marshal for certtool.h.
12. Remove the old implementation.
2009-07-02 14:08:20 +08:00

410 lines
11 KiB
C

/*
** Copyright 2009, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#define LOG_TAG "keystore"
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <utime.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <private/android_filesystem_config.h>
#include <cutils/sockets.h>
#include <cutils/log.h>
#include <cutils/properties.h>
#include "netkeystore.h"
#include "keymgmt.h"
#define CMD_PUT_WITH_FILE "putfile"
typedef void CMD_FUNC(LPC_MARSHAL *cmd, LPC_MARSHAL *reply);
struct cmdinfo {
const char *name;
CMD_FUNC *func;
};
static CMD_FUNC do_lock;
static CMD_FUNC do_unlock;
static CMD_FUNC do_passwd;
static CMD_FUNC do_get_state;;
static CMD_FUNC do_listkeys;
static CMD_FUNC do_get_key;
static CMD_FUNC do_put_key;
static CMD_FUNC do_remove_key;
static CMD_FUNC do_reset_keystore;
#define str(x) #x
struct cmdinfo cmds[] = {
{ str(LOCK), do_lock },
{ str(UNLOCK), do_unlock },
{ str(PASSWD), do_passwd },
{ str(GETSTATE), do_get_state },
{ str(LISTKEYS), do_listkeys },
{ str(GET), do_get_key },
{ str(PUT), do_put_key },
{ str(REMOVE), do_remove_key },
{ str(RESET), do_reset_keystore },
};
static struct ucred cr;
static int check_get_perm(int uid)
{
if (uid == AID_WIFI || uid == AID_VPN) return 0;
return -1;
}
static int check_reset_perm(int uid)
{
if (uid == AID_SYSTEM) return 0;
return -1;
}
static int parse_keyname(char *name, uint32_t len,
char *namespace, char *keyname)
{
int count = 0;
char *c = namespace, *p = namespace, *t = name;
if (!name || !namespace || !keyname) return -1;
while (t < name + len && (*t != 0)) {
if (*t == ' ') {
if (c == keyname) return -1;
*p = count = 0;
c = p = keyname;
t++;
} else {
if (!isalnum(*t)) return -1;
*p++ = *t++;
// also check if the keyname/namespace is too long.
if (count++ == MAX_KEY_NAME_LENGTH) return -1;
}
}
*p = 0;
return 0;
}
// args of passwd():
// firstPassword - for the first time
// oldPassword newPassword - for changing the password
static void do_passwd(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
reply->retcode = passwd((char*)cmd->data);
}
// args of lock():
// no argument
static void do_lock(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
reply->retcode = lock();
}
// args of unlock():
// password
static void do_unlock(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
reply->retcode = unlock((char*)cmd->data);
}
// args of get_state():
// no argument
static void do_get_state(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
reply->retcode = get_state();
}
// args of listkeys():
// namespace
static void do_listkeys(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
reply->retcode = list_keys((const char*)cmd->data, (char*)reply->data);
if (!reply->retcode) reply->len = strlen((char*)reply->data) + 1;
}
// args of get():
// namespace keyname
static void do_get_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
char namespace[MAX_KEY_NAME_LENGTH];
char keyname[MAX_KEY_NAME_LENGTH];
if (check_get_perm(cr.uid)) {
LOGE("uid %d doesn't have the permission to get key value\n", cr.uid);
reply->retcode = -1;
return;
}
if (parse_keyname((char*)cmd->data, cmd->len, namespace, keyname)) {
reply->retcode = -1;
} else {
reply->retcode = get_key(namespace, keyname, reply->data,
(int*)&reply->len);
}
}
static int get_value_index(LPC_MARSHAL *cmd)
{
uint32_t count = 0, i;
for (i = 0 ; i < cmd->len ; ++i) {
if (cmd->data[i] == ' ') {
if (++count == 2) return ++i;
}
}
return -1;
}
// args of put():
// namespace keyname keyvalue
static void do_put_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
char namespace[MAX_KEY_NAME_LENGTH];
char keyname[MAX_KEY_NAME_LENGTH];
int p = get_value_index(cmd);
if (p == -1) {
reply->retcode = -1;
} else {
unsigned char *value;
if (parse_keyname((char*)cmd->data, p - 1, namespace, keyname)) {
reply->retcode = -1;
return;
}
value = &cmd->data[p];
int len = cmd->len - p;
reply->retcode = put_key(namespace, keyname, value, len);
}
}
// args of remove_key():
// namespace keyname
static void do_remove_key(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
char namespace[MAX_KEY_NAME_LENGTH];
char keyname[MAX_KEY_NAME_LENGTH];
if (parse_keyname((char*)cmd->data, cmd->len, namespace, keyname)) {
reply->retcode = -1;
return;
}
reply->retcode = remove_key(namespace, keyname);
}
// args of reset_keystore():
// no argument
static void do_reset_keystore(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
if (check_reset_perm(cr.uid)) {
LOGE("uid %d doesn't have the permission to reset the keystore\n",
cr.uid);
reply->retcode = -1;
return;
}
reply->retcode = reset_keystore();
}
static void execute(LPC_MARSHAL *cmd, LPC_MARSHAL *reply)
{
uint32_t cmd_max = sizeof(cmds)/sizeof(struct cmdinfo);
if (cmd->opcode >= cmd_max) {
LOGE("the opcode (%d) is not valid", cmd->opcode);
reply->retcode = -1;
return;
}
cmds[cmd->opcode].func(cmd, reply);
}
static int set_read_timeout(int socket)
{
struct timeval tv;
tv.tv_sec = READ_TIMEOUT;
if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv))
{
LOGE("setsockopt failed");
return -1;
}
return 0;
}
static int append_input_from_file(const char *filename, LPC_MARSHAL *cmd)
{
int fd, len, ret = 0;
// get opcode of the function put()
if ((fd = open(filename, O_RDONLY)) == -1) {
fprintf(stderr, "Can not open file %s\n", filename);
return -1;
}
cmd->data[cmd->len] = ' ';
cmd->len++;
len = read(fd, cmd->data + cmd->len, BUFFER_MAX - cmd->len);
if (len < 0 || (len == (int)(BUFFER_MAX - cmd->len))) {
ret = -1;
} else {
cmd->len += len;
}
close(fd);
return ret;
}
static int flatten_str_args(int argc, const char **argv, LPC_MARSHAL *cmd)
{
int i, len = 0;
char *buf = (char*)cmd->data;
buf[0] = 0;
for (i = 0 ; i < argc ; ++i) {
if (i == 0) {
len = strlcpy(buf, argv[i], BUFFER_MAX);
} else {
len += snprintf(buf + len, BUFFER_MAX - len, " %s", argv[i]);
}
if (len >= BUFFER_MAX) return -1;
}
if (len) cmd->len = len;
return 0;
}
static int parse_cmd(int argc, const char **argv, LPC_MARSHAL *cmd)
{
uint32_t i, len = 0;
uint32_t cmd_max = sizeof(cmds)/sizeof(cmds[0]);
for (i = 0 ; i < cmd_max ; ++i) {
if (!strcasecmp(argv[0], cmds[i].name)) break;
}
if (i == cmd_max) {
// check if this is a command to put the key value with a file.
if (strcmp(argv[0], CMD_PUT_WITH_FILE) != 0) return -1;
cmd->opcode = PUT;
if (argc != 4) {
fprintf(stderr, "%s args\n\tnamespace keyname filename\n",
argv[0]);
return -1;
}
if (flatten_str_args(argc - 2, argv + 1, cmd)) return -1;
return append_input_from_file(argv[3], cmd);
} else {
cmd->opcode = i;
return flatten_str_args(argc - 1, argv + 1, cmd);
}
}
static int shell_command(const int argc, const char **argv)
{
int fd, i;
LPC_MARSHAL cmd;
if (parse_cmd(argc, argv , &cmd)) {
fprintf(stderr, "Incorrect command or command line is too long.\n");
exit(1);
}
fd = socket_local_client(SOCKET_PATH,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
if (fd == -1) {
fprintf(stderr, "Keystore service is not up and running.\n");
exit(1);
}
if (write_marshal(fd, &cmd)) {
fprintf(stderr, "Incorrect command or command line is too long.\n");
exit(1);
}
if (read_marshal(fd, &cmd)) {
fprintf(stderr, "Failed to read the result.\n");
exit(1);
}
cmd.data[cmd.len] = 0;
fprintf(stdout, "%s\n", (cmd.retcode == 0) ? "Succeeded!" : "Failed!");
if (cmd.len) fprintf(stdout, "\t%s\n", (char*)cmd.data);
close(fd);
return 0;
}
int main(const int argc, const char *argv[])
{
struct sockaddr addr;
socklen_t alen;
int lsocket, s;
LPC_MARSHAL cmd, reply;
if (argc > 1) {
return shell_command(argc - 1, argv + 1);
}
if (init_keystore(KEYSTORE_DIR)) {
LOGE("Can not initialize the keystore, the directory exist?\n");
exit(1);
}
lsocket = android_get_control_socket(SOCKET_PATH);
if (lsocket < 0) {
LOGE("Failed to get socket from environment: %s\n", strerror(errno));
exit(1);
}
if (listen(lsocket, 5)) {
LOGE("Listen on socket failed: %s\n", strerror(errno));
exit(1);
}
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
memset(&reply, 0, sizeof(LPC_MARSHAL));
for (;;) {
socklen_t cr_size = sizeof(cr);
alen = sizeof(addr);
s = accept(lsocket, &addr, &alen);
/* retrieve the caller info here */
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
close(s);
LOGE("Unable to recieve socket options\n");
continue;
}
if (s < 0) {
LOGE("Accept failed: %s\n", strerror(errno));
continue;
}
fcntl(s, F_SETFD, FD_CLOEXEC);
if (set_read_timeout(s)) {
close(s);
continue;
}
// read the command, execute and send the result back.
if(read_marshal(s, &cmd)) goto err;
LOGI("new connection\n");
execute(&cmd, &reply);
write_marshal(s, &reply);
err:
memset(&reply, 0, sizeof(LPC_MARSHAL));
LOGI("closing connection\n");
close(s);
}
return 0;
}