| /* |
| * Copyright (C) 2012 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 <string.h> |
| #include <resolv.h> |
| #include <cutils/list.h> |
| #include <cutils/sockets.h> |
| |
| #include "sysdeps.h" |
| #include "adb.h" |
| #include "adb_auth.h" |
| #include "fdevent.h" |
| #include "mincrypt/rsa.h" |
| #include "mincrypt/sha.h" |
| |
| #define TRACE_TAG TRACE_AUTH |
| |
| |
| struct adb_public_key { |
| struct listnode node; |
| RSAPublicKey key; |
| }; |
| |
| static char *key_paths[] = { |
| "/adb_keys", |
| "/data/misc/adb/adb_keys", |
| NULL |
| }; |
| |
| static fdevent listener_fde; |
| static int framework_fd = -1; |
| |
| static void usb_disconnected(void* unused, atransport* t); |
| static struct adisconnect usb_disconnect = { usb_disconnected, 0, 0, 0 }; |
| static atransport* usb_transport; |
| static bool needs_retry = false; |
| |
| static void read_keys(const char *file, struct listnode *list) |
| { |
| struct adb_public_key *key; |
| FILE *f; |
| char buf[MAX_PAYLOAD]; |
| char *sep; |
| int ret; |
| |
| f = fopen(file, "re"); |
| if (!f) { |
| D("Can't open '%s'\n", file); |
| return; |
| } |
| |
| while (fgets(buf, sizeof(buf), f)) { |
| /* Allocate 4 extra bytes to decode the base64 data in-place */ |
| key = calloc(1, sizeof(*key) + 4); |
| if (!key) { |
| D("Can't malloc key\n"); |
| break; |
| } |
| |
| sep = strpbrk(buf, " \t"); |
| if (sep) |
| *sep = '\0'; |
| |
| ret = __b64_pton(buf, (u_char *)&key->key, sizeof(key->key) + 4); |
| if (ret != sizeof(key->key)) { |
| D("%s: Invalid base64 data ret=%d\n", file, ret); |
| free(key); |
| continue; |
| } |
| |
| if (key->key.len != RSANUMWORDS) { |
| D("%s: Invalid key len %d\n", file, key->key.len); |
| free(key); |
| continue; |
| } |
| |
| list_add_tail(list, &key->node); |
| } |
| |
| fclose(f); |
| } |
| |
| static void free_keys(struct listnode *list) |
| { |
| struct listnode *item; |
| |
| while (!list_empty(list)) { |
| item = list_head(list); |
| list_remove(item); |
| free(node_to_item(item, struct adb_public_key, node)); |
| } |
| } |
| |
| static void load_keys(struct listnode *list) |
| { |
| char *path; |
| char **paths = key_paths; |
| struct stat buf; |
| |
| list_init(list); |
| |
| while ((path = *paths++)) { |
| if (!stat(path, &buf)) { |
| D("Loading keys from '%s'\n", path); |
| read_keys(path, list); |
| } |
| } |
| } |
| |
| int adb_auth_generate_token(void *token, size_t token_size) |
| { |
| FILE *f; |
| int ret; |
| |
| f = fopen("/dev/urandom", "re"); |
| if (!f) |
| return 0; |
| |
| ret = fread(token, token_size, 1, f); |
| |
| fclose(f); |
| return ret * token_size; |
| } |
| |
| int adb_auth_verify(void *token, void *sig, int siglen) |
| { |
| struct listnode *item; |
| struct adb_public_key *key; |
| struct listnode key_list; |
| int ret = 0; |
| |
| if (siglen != RSANUMBYTES) |
| return 0; |
| |
| load_keys(&key_list); |
| |
| list_for_each(item, &key_list) { |
| key = node_to_item(item, struct adb_public_key, node); |
| ret = RSA_verify(&key->key, sig, siglen, token, SHA_DIGEST_SIZE); |
| if (ret) |
| break; |
| } |
| |
| free_keys(&key_list); |
| |
| return ret; |
| } |
| |
| static void usb_disconnected(void* unused, atransport* t) |
| { |
| D("USB disconnect\n"); |
| remove_transport_disconnect(usb_transport, &usb_disconnect); |
| usb_transport = NULL; |
| needs_retry = false; |
| } |
| |
| static void adb_auth_event(int fd, unsigned events, void *data) |
| { |
| char response[2]; |
| int ret; |
| |
| if (events & FDE_READ) { |
| ret = unix_read(fd, response, sizeof(response)); |
| if (ret <= 0) { |
| D("Framework disconnect\n"); |
| if (usb_transport) |
| fdevent_remove(&usb_transport->auth_fde); |
| framework_fd = -1; |
| } |
| else if (ret == 2 && response[0] == 'O' && response[1] == 'K') { |
| if (usb_transport) |
| adb_auth_verified(usb_transport); |
| } |
| } |
| } |
| |
| void adb_auth_confirm_key(unsigned char *key, size_t len, atransport *t) |
| { |
| char msg[MAX_PAYLOAD]; |
| int ret; |
| |
| if (!usb_transport) { |
| usb_transport = t; |
| add_transport_disconnect(t, &usb_disconnect); |
| } |
| |
| if (framework_fd < 0) { |
| D("Client not connected\n"); |
| needs_retry = true; |
| return; |
| } |
| |
| if (key[len - 1] != '\0') { |
| D("Key must be a null-terminated string\n"); |
| return; |
| } |
| |
| ret = snprintf(msg, sizeof(msg), "PK%s", key); |
| if (ret >= (signed)sizeof(msg)) { |
| D("Key too long. ret=%d", ret); |
| return; |
| } |
| D("Sending '%s'\n", msg); |
| |
| ret = unix_write(framework_fd, msg, ret); |
| if (ret < 0) { |
| D("Failed to write PK, errno=%d\n", errno); |
| return; |
| } |
| |
| fdevent_install(&t->auth_fde, framework_fd, adb_auth_event, t); |
| fdevent_add(&t->auth_fde, FDE_READ); |
| } |
| |
| static void adb_auth_listener(int fd, unsigned events, void *data) |
| { |
| struct sockaddr addr; |
| socklen_t alen; |
| int s; |
| |
| alen = sizeof(addr); |
| |
| s = adb_socket_accept(fd, &addr, &alen); |
| if (s < 0) { |
| D("Failed to accept: errno=%d\n", errno); |
| return; |
| } |
| |
| framework_fd = s; |
| |
| if (needs_retry) { |
| needs_retry = false; |
| send_auth_request(usb_transport); |
| } |
| } |
| |
| void adb_auth_init(void) |
| { |
| int fd, ret; |
| |
| fd = android_get_control_socket("adbd"); |
| if (fd < 0) { |
| D("Failed to get adbd socket\n"); |
| return; |
| } |
| fcntl(fd, F_SETFD, FD_CLOEXEC); |
| |
| ret = listen(fd, 4); |
| if (ret < 0) { |
| D("Failed to listen on '%d'\n", fd); |
| return; |
| } |
| |
| fdevent_install(&listener_fde, fd, adb_auth_listener, NULL); |
| fdevent_add(&listener_fde, FDE_READ); |
| } |