blob: 7a2aa7d8ff58d2a33afaf0d0c5d2d676936c0e34 [file] [log] [blame]
/*
* lockdown.c
* libiphone built-in lockdownd client
*
* Copyright (c) 2008 Zach C. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>
#include <libtasn1.h>
#include <gnutls/x509.h>
#include <plist/plist.h>
#include "lockdown.h"
#include "iphone.h"
#include "utils.h"
#include "userpref.h"
#define RESULT_SUCCESS 0
#define RESULT_FAILURE 1
const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = {
{"PKCS1", 536872976, 0},
{0, 1073741836, 0},
{"RSAPublicKey", 536870917, 0},
{"modulus", 1073741827, 0},
{"publicExponent", 3, 0},
{0, 0, 0}
};
/**
* Internally used function for checking the result from lockdown's answer
* plist to a previously sent request.
*
* @param dict The plist to evaluate.
* @param query_match Name of the request to match.
*
* @return RESULT_SUCCESS when the result is 'Success',
* RESULT_FAILURE when the result is 'Failure',
* or a negative value if an error occured during evaluation.
*/
static int lockdown_check_result(plist_t dict, const char *query_match)
{
int ret = -1;
plist_t query_key = plist_find_node_by_key(dict, "Request");
if (!query_key) {
return ret;
}
plist_t query_node = plist_get_next_sibling(query_key);
if (!query_node) {
return ret;
}
if (plist_get_node_type(query_node) != PLIST_STRING) {
return ret;
} else {
char *query_value = NULL;
plist_get_string_val(query_node, &query_value);
if (!query_value) {
return ret;
}
if (strcmp(query_value, query_match) != 0) {
free(query_value);
return ret;
}
free(query_value);
}
plist_t result_node = plist_get_next_sibling(query_node);
if (!result_node) {
return ret;
}
plist_t value_node = plist_get_next_sibling(result_node);
if (!value_node) {
return ret;
}
plist_type result_type = plist_get_node_type(result_node);
plist_type value_type = plist_get_node_type(value_node);
if (result_type == PLIST_KEY && value_type == PLIST_STRING) {
char *result_value = NULL;
char *value_value = NULL;
plist_get_key_val(result_node, &result_value);
plist_get_string_val(value_node, &value_value);
if (result_value && value_value && !strcmp(result_value, "Result")) {
if (!strcmp(value_value, "Success")) {
ret = RESULT_SUCCESS;
} else if (!strcmp(value_value, "Failure")) {
ret = RESULT_FAILURE;
} else {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: ERROR: unknown result value '%s'\n", __func__, value_value);
}
}
if (result_value)
free(result_value);
if (value_value)
free(value_value);
}
return ret;
}
/**
* Closes the lockdownd communication session, by sending
* the StopSession Request to the device.
*
* @param control The lockdown client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "StopSession");
plist_add_sub_key_el(dict, "SessionID");
plist_add_sub_string_el(dict, session_id);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: called\n", __func__);
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_recv(client, &dict);
if (!dict) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: LOCKDOWN_E_PLIST_ERROR\n", __func__);
return LOCKDOWN_E_PLIST_ERROR;
}
ret = LOCKDOWN_E_UNKNOWN_ERROR;
if (lockdown_check_result(dict, "StopSession") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
plist_free(dict);
dict = NULL;
return ret;
}
/**
* Shuts down the SSL session by first calling iphone_lckd_stop_session
* to cleanly close the lockdownd communication session, and then
* performing a close notify, which is done by "gnutls_bye".
*
* @param client The lockdown client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
static lockdownd_error_t lockdownd_stop_ssl_session(lockdownd_client_t client)
{
if (!client) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: invalid argument!\n", __func__);
return LOCKDOWN_E_INVALID_ARG;
}
lockdownd_error_t ret = LOCKDOWN_E_SUCCESS;
if (client->in_SSL) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: stopping SSL session\n", __func__);
ret = lockdownd_stop_session(client, client->session_id);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: sending SSL close notify\n", __func__);
gnutls_bye(*client->ssl_session, GNUTLS_SHUT_RDWR);
}
if (client->ssl_session) {
gnutls_deinit(*client->ssl_session);
free(client->ssl_session);
}
client->in_SSL = 0;
return ret;
}
/** Closes the lockdownd client and does the necessary housekeeping.
*
* @param client The lockdown client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_client_free(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
lockdownd_stop_ssl_session(client);
if (client->sfd > 0) {
lockdownd_goodbye(client);
// IMO, read of final "sessionUpcall connection closed" packet
// should come here instead of in iphone_free_device
ret = usbmuxd_disconnect(client->sfd);
}
free(client);
return ret;
}
/** Polls the iPhone for lockdownd data.
*
* @param control The lockdownd client
* @param plist The plist to store the received data
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_recv(lockdownd_client_t client, plist_t *plist)
{
if (!client || !plist || (plist && *plist))
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
char *receive = NULL;
uint32_t datalen = 0, bytes = 0, received_bytes = 0;
if (!client->in_SSL)
ret = usbmuxd_recv(client->sfd, (char *) &datalen, sizeof(datalen), &bytes);
else {
ssize_t res = gnutls_record_recv(*client->ssl_session, &datalen, sizeof(datalen));
if (res < 0) {
log_dbg_msg(DBGMASK_LOCKDOWND, "gnutls_record_recv: Error occured: %s\n", gnutls_strerror(res));
return LOCKDOWN_E_SSL_ERROR;
} else {
bytes = res;
ret = LOCKDOWN_E_SUCCESS;
}
}
datalen = ntohl(datalen);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: datalen = %d\n", __func__, datalen);
receive = (char *) malloc(sizeof(char) * datalen);
/* fill buffer and request more packets if needed */
if (!client->in_SSL) {
while ((received_bytes < datalen) && (ret == LOCKDOWN_E_SUCCESS)) {
ret = usbmuxd_recv(client->sfd, receive + received_bytes, datalen - received_bytes, &bytes);
received_bytes += bytes;
}
} else {
ssize_t res = 0;
while ((received_bytes < datalen) && (ret == LOCKDOWN_E_SUCCESS)) {
res = gnutls_record_recv(*client->ssl_session, receive + received_bytes, datalen - received_bytes);
if (res < 0) {
log_dbg_msg(DBGMASK_LOCKDOWND, "gnutls_record_recv: Error occured: %s\n", gnutls_strerror(res));
ret = LOCKDOWN_E_SSL_ERROR;
} else {
received_bytes += res;
ret = LOCKDOWN_E_SUCCESS;
}
}
}
if (ret != LOCKDOWN_E_SUCCESS) {
free(receive);
return ret;
}
if ((ssize_t)received_bytes <= 0) {
free(receive);
return LOCKDOWN_E_NOT_ENOUGH_DATA;
}
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: received msg size: %i, buffer follows:\n%s", __func__, received_bytes, receive);
plist_from_xml(receive, received_bytes, plist);
free(receive);
if (!*plist)
ret = LOCKDOWN_E_PLIST_ERROR;
return ret;
}
/** Sends lockdownd data to the iPhone
*
* @note This function is low-level and should only be used if you need to send
* a new type of message.
*
* @param client The lockdownd client
* @param plist The plist to send
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist)
{
if (!client || !plist)
return LOCKDOWN_E_INVALID_ARG;
char *real_query;
int bytes;
char *XMLContent = NULL;
uint32_t length = 0;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_to_xml(plist, &XMLContent, &length);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: sending msg size %i, buffer follows:\n%s", __func__, length, XMLContent);
real_query = (char *) malloc(sizeof(char) * (length + 4));
length = htonl(length);
memcpy(real_query, &length, sizeof(length));
memcpy(real_query + 4, XMLContent, ntohl(length));
free(XMLContent);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: made the query, sending it along\n", __func__);
if (!client->in_SSL)
ret = usbmuxd_send(client->sfd, real_query, ntohl(length) + sizeof(length), (uint32_t*)&bytes);
else {
ssize_t res = gnutls_record_send(*client->ssl_session, real_query, ntohl(length) + sizeof(length));
if (res < 0) {
log_dbg_msg(DBGMASK_LOCKDOWND, "gnutls_record_send: Error occured: %s\n", gnutls_strerror(res));
ret = LOCKDOWN_E_SSL_ERROR;
} else {
bytes = res;
ret = LOCKDOWN_E_SUCCESS;
}
}
if (ret == LOCKDOWN_E_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: sent it!\n", __func__);
} else {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: sending failed!\n", __func__);
}
free(real_query);
return ret;
}
/** Initiates the handshake for the lockdown session. Part of the lockdownd handshake.
*
* @param client The lockdownd client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_query_type(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "QueryType");
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: called\n", __func__);
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_recv(client, &dict);
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
ret = LOCKDOWN_E_UNKNOWN_ERROR;
if (lockdown_check_result(dict, "QueryType") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
plist_free(dict);
dict = NULL;
return ret;
}
/** Retrieves a preferences plist using an optional domain and/or key name.
*
* @param client an initialized lockdownd client.
* @param domain the domain to query on or NULL for global domain
* @param key the key name to request or NULL to query for all keys
* @param value a plist node representing the result value node
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
if (domain) {
plist_add_sub_key_el(dict, "Domain");
plist_add_sub_string_el(dict, domain);
}
if (key) {
plist_add_sub_key_el(dict, "Key");
plist_add_sub_string_el(dict, key);
}
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "GetValue");
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_recv(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
if (lockdown_check_result(dict, "GetValue") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_t value_key_node = plist_find_node_by_key(dict, "Value");
plist_t value_value_node = plist_get_next_sibling(value_key_node);
plist_type value_key_type = plist_get_node_type(value_key_node);
if (value_key_type == PLIST_KEY) {
char *result_key = NULL;
plist_get_key_val(value_key_node, &result_key);
if (!strcmp(result_key, "Value")) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: has a value\n", __func__);
*value = plist_copy(value_value_node);
}
free(result_key);
}
plist_free(dict);
return ret;
}
/** Sets a preferences value using a plist and optional domain and/or key name.
*
* @param client an initialized lockdownd client.
* @param domain the domain to query on or NULL for global domain
* @param key the key name to set the value or NULL to set a value dict plist
* @param value a plist node of any node type representing the value to set
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value)
{
if (!client || !value)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
if (domain) {
plist_add_sub_key_el(dict, "Domain");
plist_add_sub_string_el(dict, domain);
}
if (key) {
plist_add_sub_key_el(dict, "Key");
plist_add_sub_string_el(dict, key);
}
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "SetValue");
plist_add_sub_key_el(dict, "Value");
plist_add_sub_node(dict, value);
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_recv(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
if (lockdown_check_result(dict, "SetValue") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_free(dict);
return ret;
}
/** Removes a preference node on the device by domain and/or key name
*
* @note: Use with caution as this could remove vital information on the device
*
* @param client an initialized lockdownd client.
* @param domain the domain to query on or NULL for global domain
* @param key the key name to remove or NULL remove all keys for the current domain
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
if (domain) {
plist_add_sub_key_el(dict, "Domain");
plist_add_sub_string_el(dict, domain);
}
if (key) {
plist_add_sub_key_el(dict, "Key");
plist_add_sub_string_el(dict, key);
}
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "RemoveValue");
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_recv(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
if (lockdown_check_result(dict, "RemoveValue") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_free(dict);
return ret;
}
/** Asks for the device's unique id. Part of the lockdownd handshake.
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uuid)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
ret = lockdownd_get_value(client, NULL, "UniqueDeviceID", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_string_val(value, uuid);
plist_free(value);
value = NULL;
return ret;
}
/** Askes for the device's public key. Part of the lockdownd handshake.
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnutls_datum_t * public_key)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
char *value_value = NULL;
uint64_t size = 0;
ret = lockdownd_get_value(client, NULL, "DevicePublicKey", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_data_val(value, &value_value, &size);
public_key->data = (unsigned char*)value_value;
public_key->size = size;
plist_free(value);
value = NULL;
return ret;
}
/** Askes for the device's name.
*
* @param client The pointer to the location of the new lockdownd_client
*
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
ret = lockdownd_get_value(client, NULL, "DeviceName", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_string_val(value, device_name);
plist_free(value);
value = NULL;
return ret;
}
/** Creates a lockdownd client for the give iPhone
*
* @param phone The iPhone to create a lockdownd client for
* @param client The pointer to the location of the new lockdownd_client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_client_new(iphone_device_t device, lockdownd_client_t *client)
{
if (!device || !client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_SUCCESS;
char *host_id = NULL;
int sfd = usbmuxd_connect(device->handle, 0xf27e);
if (sfd < 0) {
log_debug_msg("%s: could not connect to lockdownd (device handle %d)\n", __func__, device->handle);
return LOCKDOWN_E_MUX_ERROR;
}
lockdownd_client_t client_loc = (lockdownd_client_t) malloc(sizeof(struct lockdownd_client_int));
client_loc->sfd = sfd;
client_loc->ssl_session = (gnutls_session_t *) malloc(sizeof(gnutls_session_t));
client_loc->in_SSL = 0;
if (LOCKDOWN_E_SUCCESS != lockdownd_query_type(client_loc)) {
log_debug_msg("%s: QueryType failed in the lockdownd client.\n", __func__);
ret = LOCKDOWN_E_NOT_ENOUGH_DATA;
}
char *uuid = NULL;
ret = iphone_device_get_uuid(device, &uuid);
if (LOCKDOWN_E_SUCCESS != ret) {
log_debug_msg("%s: failed to get device uuid.\n", __func__);
}
log_debug_msg("%s: device uuid: %s\n", __func__, uuid);
userpref_get_host_id(&host_id);
if (LOCKDOWN_E_SUCCESS == ret && !host_id) {
ret = LOCKDOWN_E_INVALID_CONF;
}
if (LOCKDOWN_E_SUCCESS == ret && !userpref_has_device_public_key(uuid))
ret = lockdownd_pair(client_loc, uuid, host_id);
if (uuid) {
free(uuid);
uuid = NULL;
}
if (LOCKDOWN_E_SUCCESS == ret) {
ret = lockdownd_start_ssl_session(client_loc, host_id);
if (LOCKDOWN_E_SUCCESS != ret) {
ret = LOCKDOWN_E_SSL_ERROR;
log_debug_msg("%s: SSL Session opening failed.\n", __func__);
}
if (host_id) {
free(host_id);
host_id = NULL;
}
if (LOCKDOWN_E_SUCCESS == ret)
*client = client_loc;
}
return ret;
}
/** Generates the appropriate keys and pairs the device. It's part of the
* lockdownd handshake.
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_pair(lockdownd_client_t client, char *uuid, char *host_id)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = NULL;
plist_t dict_record = NULL;
gnutls_datum_t device_cert = { NULL, 0 };
gnutls_datum_t host_cert = { NULL, 0 };
gnutls_datum_t root_cert = { NULL, 0 };
gnutls_datum_t public_key = { NULL, 0 };
ret = lockdownd_get_device_public_key(client, &public_key);
if (ret != LOCKDOWN_E_SUCCESS) {
log_debug_msg("%s: device refused to send public key.\n", __func__);
return ret;
}
log_debug_msg("%s: device public key follows:\n%s\n", __func__, public_key.data);
ret = lockdownd_gen_pair_cert(public_key, &device_cert, &host_cert, &root_cert);
if (ret != LOCKDOWN_E_SUCCESS) {
free(public_key.data);
return ret;
}
/* Setup Pair request plist */
dict = plist_new_dict();
plist_add_sub_key_el(dict, "PairRecord");
dict_record = plist_new_dict();
plist_add_sub_node(dict, dict_record);
plist_add_sub_key_el(dict_record, "DeviceCertificate");
plist_add_sub_data_el(dict_record, (const char*)device_cert.data, device_cert.size);
plist_add_sub_key_el(dict_record, "HostCertificate");
plist_add_sub_data_el(dict_record, (const char*)host_cert.data, host_cert.size);
plist_add_sub_key_el(dict_record, "HostID");
plist_add_sub_string_el(dict_record, host_id);
plist_add_sub_key_el(dict_record, "RootCertificate");
plist_add_sub_data_el(dict_record, (const char*)root_cert.data, root_cert.size);
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "Pair");
/* send to iPhone */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get iPhone's answer */
ret = lockdownd_recv(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
if (lockdown_check_result(dict, "Pair") == RESULT_SUCCESS) {
ret = LOCKDOWN_E_SUCCESS;
}
plist_free(dict);
dict = NULL;
/* store public key in config if pairing succeeded */
if (ret == LOCKDOWN_E_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: pair success\n", __func__);
userpref_set_device_public_key(uuid, public_key);
} else {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: pair failure\n", __func__);
ret = LOCKDOWN_E_PAIRING_FAILED;
}
free(public_key.data);
return ret;
}
/**
* Tells the device to immediately enter recovery mode.
*
* @param client The lockdown client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "EnterRecovery");
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: telling device to enter recovery mode\n", __func__);
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_recv(client, &dict);
if (lockdown_check_result(dict, "EnterRecovery") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
plist_free(dict);
dict = NULL;
return ret;
}
/**
* Performs the Goodbye Request to tell the device the communication
* session is now closed.
*
* @param client The lockdown client
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "Goodbye");
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: called\n", __func__);
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_recv(client, &dict);
if (!dict) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: did not get goodbye response back\n", __func__);
return LOCKDOWN_E_PLIST_ERROR;
}
if (lockdown_check_result(dict, "Goodbye") == RESULT_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: success\n", __func__);
ret = LOCKDOWN_E_SUCCESS;
}
plist_free(dict);
dict = NULL;
return ret;
}
/** Generates the device certificate from the public key as well as the host
* and root certificates.
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_gen_pair_cert(gnutls_datum_t public_key, gnutls_datum_t * odevice_cert,
gnutls_datum_t * ohost_cert, gnutls_datum_t * oroot_cert)
{
if (!public_key.data || !odevice_cert || !ohost_cert || !oroot_cert)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
userpref_error_t uret = USERPREF_E_UNKNOWN_ERROR;
gnutls_datum_t modulus = { NULL, 0 };
gnutls_datum_t exponent = { NULL, 0 };
/* now decode the PEM encoded key */
gnutls_datum_t der_pub_key;
if (GNUTLS_E_SUCCESS == gnutls_pem_base64_decode_alloc("RSA PUBLIC KEY", &public_key, &der_pub_key)) {
/* initalize asn.1 parser */
ASN1_TYPE pkcs1 = ASN1_TYPE_EMPTY;
if (ASN1_SUCCESS == asn1_array2tree(pkcs1_asn1_tab, &pkcs1, NULL)) {
ASN1_TYPE asn1_pub_key = ASN1_TYPE_EMPTY;
asn1_create_element(pkcs1, "PKCS1.RSAPublicKey", &asn1_pub_key);
if (ASN1_SUCCESS == asn1_der_decoding(&asn1_pub_key, der_pub_key.data, der_pub_key.size, NULL)) {
/* get size to read */
int ret1 = asn1_read_value(asn1_pub_key, "modulus", NULL, (int*)&modulus.size);
int ret2 = asn1_read_value(asn1_pub_key, "publicExponent", NULL, (int*)&exponent.size);
modulus.data = gnutls_malloc(modulus.size);
exponent.data = gnutls_malloc(exponent.size);
ret1 = asn1_read_value(asn1_pub_key, "modulus", modulus.data, (int*)&modulus.size);
ret2 = asn1_read_value(asn1_pub_key, "publicExponent", exponent.data, (int*)&exponent.size);
if (ASN1_SUCCESS == ret1 && ASN1_SUCCESS == ret2)
ret = LOCKDOWN_E_SUCCESS;
}
if (asn1_pub_key)
asn1_delete_structure(&asn1_pub_key);
}
if (pkcs1)
asn1_delete_structure(&pkcs1);
}
/* now generate certifcates */
if (LOCKDOWN_E_SUCCESS == ret && 0 != modulus.size && 0 != exponent.size) {
gnutls_global_init();
gnutls_datum_t essentially_null = { (unsigned char*)strdup("abababababababab"), strlen("abababababababab") };
gnutls_x509_privkey_t fake_privkey, root_privkey, host_privkey;
gnutls_x509_crt_t dev_cert, root_cert, host_cert;
gnutls_x509_privkey_init(&fake_privkey);
gnutls_x509_crt_init(&dev_cert);
gnutls_x509_crt_init(&root_cert);
gnutls_x509_crt_init(&host_cert);
if (GNUTLS_E_SUCCESS ==
gnutls_x509_privkey_import_rsa_raw(fake_privkey, &modulus, &exponent, &essentially_null, &essentially_null,
&essentially_null, &essentially_null)) {
gnutls_x509_privkey_init(&root_privkey);
gnutls_x509_privkey_init(&host_privkey);
uret = userpref_get_keys_and_certs(root_privkey, root_cert, host_privkey, host_cert);
if (USERPREF_E_SUCCESS == uret) {
/* generate device certificate */
gnutls_x509_crt_set_key(dev_cert, fake_privkey);
gnutls_x509_crt_set_serial(dev_cert, "\x00", 1);
gnutls_x509_crt_set_version(dev_cert, 3);
gnutls_x509_crt_set_ca_status(dev_cert, 0);
gnutls_x509_crt_set_activation_time(dev_cert, time(NULL));
gnutls_x509_crt_set_expiration_time(dev_cert, time(NULL) + (60 * 60 * 24 * 365 * 10));
gnutls_x509_crt_sign(dev_cert, root_cert, root_privkey);
if (LOCKDOWN_E_SUCCESS == ret) {
/* if everything went well, export in PEM format */
size_t export_size = 0;
gnutls_datum_t dev_pem = { NULL, 0 };
gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, NULL, &export_size);
dev_pem.data = gnutls_malloc(export_size);
gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, dev_pem.data, &export_size);
dev_pem.size = export_size;
gnutls_datum_t pem_root_cert = { NULL, 0 };
gnutls_datum_t pem_host_cert = { NULL, 0 };
uret = userpref_get_certs_as_pem(&pem_root_cert, &pem_host_cert);
if (USERPREF_E_SUCCESS == uret) {
/* copy buffer for output */
odevice_cert->data = malloc(dev_pem.size);
memcpy(odevice_cert->data, dev_pem.data, dev_pem.size);
odevice_cert->size = dev_pem.size;
ohost_cert->data = malloc(pem_host_cert.size);
memcpy(ohost_cert->data, pem_host_cert.data, pem_host_cert.size);
ohost_cert->size = pem_host_cert.size;
oroot_cert->data = malloc(pem_root_cert.size);
memcpy(oroot_cert->data, pem_root_cert.data, pem_root_cert.size);
oroot_cert->size = pem_root_cert.size;
g_free(pem_root_cert.data);
g_free(pem_host_cert.data);
}
}
}
switch(uret) {
case USERPREF_E_INVALID_ARG:
ret = LOCKDOWN_E_INVALID_ARG;
break;
case USERPREF_E_INVALID_CONF:
ret = LOCKDOWN_E_INVALID_CONF;
break;
case USERPREF_E_SSL_ERROR:
ret = LOCKDOWN_E_SSL_ERROR;
default:
break;
}
}
}
gnutls_free(modulus.data);
gnutls_free(exponent.data);
gnutls_free(der_pub_key.data);
return ret;
}
/** Starts SSL communication with lockdownd after the iPhone has been paired.
*
* @param client The lockdownd client
* @param HostID The HostID used with this phone
*
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_start_ssl_session(lockdownd_client_t client, const char *HostID)
{
plist_t dict = NULL;
uint32_t return_me = 0;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
client->session_id[0] = '\0';
/* Setup DevicePublicKey request plist */
dict = plist_new_dict();
plist_add_sub_key_el(dict, "HostID");
plist_add_sub_string_el(dict, HostID);
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "StartSession");
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
ret = lockdownd_recv(client, &dict);
if (!dict)
return LOCKDOWN_E_PLIST_ERROR;
if (lockdown_check_result(dict, "StartSession") == RESULT_FAILURE) {
plist_t error_node = plist_get_dict_el_from_key(dict, "Error");
if (error_node && PLIST_STRING == plist_get_node_type(error_node)) {
char *error = NULL;
plist_get_string_val(error_node, &error);
if (!strcmp(error, "InvalidHostID")) {
/* hostid is unknown. Pair and try again */
char *uuid = NULL;
char *host_id = NULL;
userpref_get_host_id(&host_id);
if (LOCKDOWN_E_SUCCESS == lockdownd_get_device_uuid(client, &uuid) ) {
if (LOCKDOWN_E_SUCCESS == lockdownd_pair(client, uuid, host_id) ) {
/* start session again */
plist_free(dict);
dict = plist_new_dict();
plist_add_sub_key_el(dict, "HostID");
plist_add_sub_string_el(dict, HostID);
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "StartSession");
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_recv(client, &dict);
}
}
free(uuid);
free(host_id);
}
free(error);
}
}
ret = LOCKDOWN_E_SSL_ERROR;
if (lockdown_check_result(dict, "StartSession") == RESULT_SUCCESS) {
// Set up GnuTLS...
//gnutls_anon_client_credentials_t anoncred;
gnutls_certificate_credentials_t xcred;
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: started the session OK, now trying GnuTLS\n", __func__);
errno = 0;
gnutls_global_init();
//gnutls_anon_allocate_client_credentials(&anoncred);
gnutls_certificate_allocate_credentials(&xcred);
gnutls_certificate_set_x509_trust_file(xcred, "hostcert.pem", GNUTLS_X509_FMT_PEM);
gnutls_init(client->ssl_session, GNUTLS_CLIENT);
{
int protocol_priority[16] = { GNUTLS_SSL3, 0 };
int kx_priority[16] = { GNUTLS_KX_ANON_DH, GNUTLS_KX_RSA, 0 };
int cipher_priority[16] = { GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_AES_256_CBC, 0 };
int mac_priority[16] = { GNUTLS_MAC_SHA1, GNUTLS_MAC_MD5, 0 };
int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
gnutls_cipher_set_priority(*client->ssl_session, cipher_priority);
gnutls_compression_set_priority(*client->ssl_session, comp_priority);
gnutls_kx_set_priority(*client->ssl_session, kx_priority);
gnutls_protocol_set_priority(*client->ssl_session, protocol_priority);
gnutls_mac_set_priority(*client->ssl_session, mac_priority);
}
gnutls_credentials_set(*client->ssl_session, GNUTLS_CRD_CERTIFICATE, xcred); // this part is killing me.
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS step 1...\n", __func__);
gnutls_transport_set_ptr(*client->ssl_session, (gnutls_transport_ptr_t) client);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS step 2...\n", __func__);
gnutls_transport_set_push_function(*client->ssl_session, (gnutls_push_func) & lockdownd_secuwrite);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS step 3...\n", __func__);
gnutls_transport_set_pull_function(*client->ssl_session, (gnutls_pull_func) & lockdownd_securead);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS step 4 -- now handshaking...\n", __func__);
if (errno)
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: WARN: errno says %s before handshake!\n", __func__, strerror(errno));
return_me = gnutls_handshake(*client->ssl_session);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS handshake done...\n", __func__);
if (return_me != GNUTLS_E_SUCCESS) {
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: GnuTLS reported something wrong.\n", __func__);
gnutls_perror(return_me);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: oh.. errno says %s\n", __func__, strerror(errno));
return LOCKDOWN_E_SSL_ERROR;
} else {
client->in_SSL = 1;
ret = LOCKDOWN_E_SUCCESS;
}
}
/* store session id */
plist_t session_node = plist_find_node_by_key(dict, "SessionID");
if (session_node) {
plist_t session_node_val = plist_get_next_sibling(session_node);
plist_type session_node_val_type = plist_get_node_type(session_node_val);
if (session_node_val_type == PLIST_STRING) {
char *session_id = NULL;
plist_get_string_val(session_node_val, &session_id);
if (session_node_val_type == PLIST_STRING && session_id) {
/* we need to store the session ID for StopSession */
strcpy(client->session_id, session_id);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: SessionID: %s\n", __func__, client->session_id);
}
if (session_id)
free(session_id);
}
} else
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: Failed to get SessionID!\n", __func__);
plist_free(dict);
dict = NULL;
if (ret == LOCKDOWN_E_SUCCESS)
return ret;
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: Apparently failed negotiating with lockdownd.\n", __func__);
return LOCKDOWN_E_SSL_ERROR;
}
/** gnutls callback for writing data to the iPhone.
*
* @param transport It's really the lockdownd client, but the method signature has to match
* @param buffer The data to send
* @param length The length of data to send in bytes
*
* @return The number of bytes sent
*/
ssize_t lockdownd_secuwrite(gnutls_transport_ptr_t transport, char *buffer, size_t length)
{
uint32_t bytes = 0;
lockdownd_client_t client;
client = (lockdownd_client_t) transport;
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: called\n", __func__);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: pre-send length = %zi\n", __func__, length);
usbmuxd_send(client->sfd, buffer, length, &bytes);
log_dbg_msg(DBGMASK_LOCKDOWND, "%s: post-send sent %i bytes\n", __func__, bytes);
return bytes;
}
/** gnutls callback for reading data from the iPhone
*
* @param transport It's really the lockdownd client, but the method signature has to match
* @param buffer The buffer to store data in
* @param length The length of data to read in bytes
*
* @return The number of bytes read
*/
ssize_t lockdownd_securead(gnutls_transport_ptr_t transport, char *buffer, size_t length)
{
int bytes = 0, pos_start_fill = 0;
size_t tbytes = 0;
int this_len = length;
iphone_error_t res;
lockdownd_client_t client;
client = (lockdownd_client_t) transport;
char *recv_buffer;
log_debug_msg("%s: pre-read client wants %zi bytes\n", __func__, length);
recv_buffer = (char *) malloc(sizeof(char) * this_len);
// repeat until we have the full data or an error occurs.
do {
if ((res = usbmuxd_recv(client->sfd, recv_buffer, this_len, (uint32_t*)&bytes)) != LOCKDOWN_E_SUCCESS) {
log_debug_msg("%s: ERROR: usbmux_recv returned %d\n", __func__, res);
return res;
}
log_debug_msg("%s: post-read we got %i bytes\n", __func__, bytes);
// increase read count
tbytes += bytes;
// fill the buffer with what we got right now
memcpy(buffer + pos_start_fill, recv_buffer, bytes);
pos_start_fill += bytes;
if (tbytes >= length) {
break;
}
this_len = length - tbytes;
log_debug_msg("%s: re-read trying to read missing %i bytes\n", __func__, this_len);
} while (tbytes < length);
if (recv_buffer) {
free(recv_buffer);
}
return tbytes;
}
/** Command to start the desired service
*
* @param client The lockdownd client
* @param service The name of the service to start
* @param port The port number the service was started on
* @return an error code (LOCKDOWN_E_SUCCESS on success)
*/
lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *service, int *port)
{
if (!client || !service || !port)
return LOCKDOWN_E_INVALID_ARG;
char *host_id = NULL;
userpref_get_host_id(&host_id);
if (!host_id)
return LOCKDOWN_E_INVALID_CONF;
if (!client->in_SSL && !lockdownd_start_ssl_session(client, host_id))
return LOCKDOWN_E_SSL_ERROR;
plist_t dict = NULL;
uint32_t port_loc = 0;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
free(host_id);
host_id = NULL;
dict = plist_new_dict();
plist_add_sub_key_el(dict, "Request");
plist_add_sub_string_el(dict, "StartService");
plist_add_sub_key_el(dict, "Service");
plist_add_sub_string_el(dict, service);
/* send to iPhone */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
ret = lockdownd_recv(client, &dict);
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
if (!dict)
return LOCKDOWN_E_PLIST_ERROR;
ret = LOCKDOWN_E_UNKNOWN_ERROR;
if (lockdown_check_result(dict, "StartService") == RESULT_SUCCESS) {
plist_t port_key_node = plist_find_node_by_key(dict, "Port");
plist_t port_value_node = plist_get_next_sibling(port_key_node);
if ((plist_get_node_type(port_key_node) == PLIST_KEY)
&& (plist_get_node_type(port_value_node) == PLIST_UINT)) {
char *port_key = NULL;
uint64_t port_value = 0;
plist_get_key_val(port_key_node, &port_key);
plist_get_uint_val(port_value_node, &port_value);
if (port_key && !strcmp(port_key, "Port")) {
port_loc = port_value;
ret = LOCKDOWN_E_SUCCESS;
}
if (port_key)
free(port_key);
if (port && ret == LOCKDOWN_E_SUCCESS)
*port = port_loc;
}
}
else
ret = LOCKDOWN_E_START_SERVICE_FAILED;
plist_free(dict);
dict = NULL;
return ret;
}