/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ofono.h" #include "simutil.h" #include "util.h" #define SIM_AUTH_MAX_RANDS 3 /* * Temporary handle used for the command authentication sequence. */ struct auth_request { /* DBus values for GSM authentication */ DBusMessage *msg; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; /* ID from open_channel */ int session_id; /* list of rands to calculate key (1 if umts == 1) */ void *rands[SIM_AUTH_MAX_RANDS]; int num_rands; /* number of keys that have been returned */ int cb_count; void *autn; uint8_t umts : 1; unsigned int watch_id; struct ofono_sim_aid_session *session; }; struct aid_object { uint8_t aid[16]; char *path; enum sim_app_type type; }; struct ofono_sim_auth { struct ofono_sim *sim; const struct ofono_sim_auth_driver *driver; void *driver_data; struct ofono_atom *atom; GSList *aid_objects; uint8_t gsm_access : 1; uint8_t gsm_context : 1; struct auth_request *pending; char *nai; }; /* * Find an application by path. 'path' should be a DBusMessage object path. */ static uint8_t *find_aid_by_path(GSList *aid_objects, const char *path) { GSList *iter = aid_objects; while (iter) { struct aid_object *obj = iter->data; if (!strcmp(path, obj->path)) return obj->aid; iter = g_slist_next(iter); } return NULL; } /* * Free all discovered AID's */ static void free_apps(struct ofono_sim_auth *sa) { DBusConnection *conn = ofono_dbus_get_connection(); struct ofono_modem *modem = __ofono_atom_get_modem(sa->atom); const char *path = __ofono_atom_get_path(sa->atom); GSList *iter = sa->aid_objects; while (iter) { struct aid_object *obj = iter->data; if (obj->type == SIM_APP_TYPE_USIM) g_dbus_unregister_interface(conn, obj->path, OFONO_USIM_APPLICATION_INTERFACE); else if (obj->type == SIM_APP_TYPE_ISIM) g_dbus_unregister_interface(conn, obj->path, OFONO_ISIM_APPLICATION_INTERFACE); g_free(obj->path); g_free(obj); iter = g_slist_next(iter); } g_dbus_unregister_interface(conn, path, OFONO_SIM_AUTHENTICATION_INTERFACE); ofono_modem_remove_interface(modem, OFONO_SIM_AUTHENTICATION_INTERFACE); g_slist_free(sa->aid_objects); } static void sim_auth_unregister(struct ofono_atom *atom) { struct ofono_sim_auth *sa = __ofono_atom_get_data(atom); free_apps(sa); g_free(sa->nai); if (sa->pending) { __ofono_dbus_pending_reply(&sa->pending->msg, __ofono_error_sim_not_ready(sa->pending->msg)); __ofono_sim_remove_session_watch(sa->pending->session, sa->pending->watch_id); g_free(sa->pending); sa->pending = NULL; } } static void sim_auth_remove(struct ofono_atom *atom) { struct ofono_sim_auth *sa = __ofono_atom_get_data(atom); DBG("atom: %p", atom); if (sa == NULL) return; g_free(sa); } /* * appends {oa{sv}} into an existing dict array */ static void append_dict_application(DBusMessageIter *iter, const char *path, const char *type, const char *name) { DBusMessageIter array; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &array); ofono_dbus_dict_append(&array, "Type", DBUS_TYPE_STRING, &type); ofono_dbus_dict_append(&array, "Name", DBUS_TYPE_STRING, &name); dbus_message_iter_close_container(iter, &array); } /* * appends a{say} onto an existing dict array */ static void append_dict_byte_array(DBusMessageIter *iter, const char *key, const void *arr, uint32_t len) { DBusMessageIter keyiter; DBusMessageIter valueiter; dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &keyiter); dbus_message_iter_append_basic(&keyiter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&keyiter, DBUS_TYPE_ARRAY, "y", &valueiter); dbus_message_iter_append_fixed_array(&valueiter, DBUS_TYPE_BYTE, &arr, len); dbus_message_iter_close_container(&keyiter, &valueiter); dbus_message_iter_close_container(iter, &keyiter); } static void handle_umts(struct ofono_sim_auth *sa, const uint8_t *resp, uint16_t len) { DBusMessage *reply = NULL; DBusMessageIter iter; DBusMessageIter dict; const uint8_t *res = NULL; const uint8_t *ck = NULL; const uint8_t *ik = NULL; const uint8_t *auts = NULL; const uint8_t *kc = NULL; if (!sim_parse_umts_authenticate(resp, len, &res, &ck, &ik, &auts, &kc)) goto umts_end; reply = dbus_message_new_method_return(sa->pending->msg); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{say}", &dict); if (auts) { append_dict_byte_array(&dict, "AUTS", auts, 14); } else { append_dict_byte_array(&dict, "RES", res, 8); append_dict_byte_array(&dict, "CK", ck, 16); append_dict_byte_array(&dict, "IK", ik, 16); if (kc) append_dict_byte_array(&dict, "Kc", kc, 8); } dbus_message_iter_close_container(&iter, &dict); umts_end: if (!reply) reply = __ofono_error_not_supported(sa->pending->msg); __ofono_dbus_pending_reply(&sa->pending->msg, reply); __ofono_sim_remove_session_watch(sa->pending->session, sa->pending->watch_id); g_free(sa->pending); sa->pending = NULL; } static void handle_gsm(struct ofono_sim_auth *sa, const uint8_t *resp, uint16_t len) { DBusMessageIter iter; const uint8_t *sres = NULL; const uint8_t *kc = NULL; if (!sim_parse_gsm_authenticate(resp, len, &sres, &kc)) goto gsm_end; /* initial iteration, setup the reply message */ if (sa->pending->cb_count == 0) { sa->pending->reply = dbus_message_new_method_return( sa->pending->msg); dbus_message_iter_init_append(sa->pending->reply, &sa->pending->iter); dbus_message_iter_open_container(&sa->pending->iter, DBUS_TYPE_ARRAY, "a{say}", &sa->pending->dict); } /* append the Nth sres/kc byte arrays */ dbus_message_iter_open_container(&sa->pending->dict, DBUS_TYPE_ARRAY, "{say}", &iter); append_dict_byte_array(&iter, "SRES", sres, 4); append_dict_byte_array(&iter, "Kc", kc, 8); dbus_message_iter_close_container(&sa->pending->dict, &iter); sa->pending->cb_count++; /* calculated the number of keys requested, close container */ if (sa->pending->cb_count == sa->pending->num_rands) { dbus_message_iter_close_container(&sa->pending->iter, &sa->pending->dict); goto gsm_end; } return; gsm_end: if (!sa->pending->reply) sa->pending->reply = __ofono_error_not_supported( sa->pending->msg); __ofono_dbus_pending_reply(&sa->pending->msg, sa->pending->reply); __ofono_sim_remove_session_watch(sa->pending->session, sa->pending->watch_id); g_free(sa->pending); sa->pending = NULL; } static void logical_access_cb(const struct ofono_error *error, const unsigned char *resp, unsigned int len, void *data) { struct ofono_sim_auth *sa = data; /* error must have occurred in a previous CB */ if (!sa->pending) return; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { __ofono_dbus_pending_reply(&sa->pending->msg, __ofono_error_failed(sa->pending->msg)); g_free(sa->pending); sa->pending = NULL; return; } if (sa->pending->umts) handle_umts(sa, resp, len); else handle_gsm(sa, resp, len); } static void get_session_cb(ofono_bool_t active, int session_id, void *data) { struct ofono_sim_auth *sa = data; int i; if (!active) goto error; /* save session ID for close_channel() */ sa->pending->session_id = session_id; /* * This will do the logical access num_rand times, providing a new * RAND seed each time. In the UMTS case, num_rands should be 1. */ for (i = 0; i < sa->pending->num_rands; i++) { uint8_t auth_cmd[40]; int len = 0; if (sa->pending->umts) len = sim_build_umts_authenticate(auth_cmd, 40, sa->pending->rands[i], sa->pending->autn); else len = sim_build_gsm_authenticate(auth_cmd, 40, sa->pending->rands[i]); if (!len) goto error; ofono_sim_logical_access(sa->sim, session_id, auth_cmd, len, logical_access_cb, sa); } return; error: __ofono_dbus_pending_reply(&sa->pending->msg, __ofono_error_failed(sa->pending->msg)); g_free(sa->pending); sa->pending = NULL; } static DBusMessage *usim_gsm_authenticate(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_sim_auth *sa = data; DBusMessageIter iter; DBusMessageIter array; uint8_t *aid; if (sa->pending) return __ofono_error_busy(msg); dbus_message_iter_init(msg, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) return __ofono_error_invalid_format(msg); sa->pending = g_new0(struct auth_request, 1); dbus_message_iter_recurse(&iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_ARRAY) { int nelement; DBusMessageIter in; dbus_message_iter_recurse(&array, &in); if (dbus_message_iter_get_arg_type(&in) != DBUS_TYPE_BYTE || sa->pending->num_rands == SIM_AUTH_MAX_RANDS) goto format_error; dbus_message_iter_get_fixed_array(&in, &sa->pending->rands[sa->pending->num_rands++], &nelement); if (nelement != 16) goto format_error; dbus_message_iter_next(&array); } if (sa->pending->num_rands < 2) goto format_error; /* * retrieve session from SIM */ aid = find_aid_by_path(sa->aid_objects, dbus_message_get_path(msg)); sa->pending->session = __ofono_sim_get_session_by_aid(sa->sim, aid); sa->pending->msg = dbus_message_ref(msg); sa->pending->watch_id = __ofono_sim_add_session_watch( sa->pending->session, get_session_cb, sa, NULL); return NULL; format_error: g_free(sa->pending); sa->pending = NULL; return __ofono_error_invalid_format(msg); } static DBusMessage *umts_common(DBusConnection *conn, DBusMessage *msg, void *data, enum sim_app_type type) { uint8_t *rand = NULL; uint8_t *autn = NULL; uint32_t rlen; uint32_t alen; struct ofono_sim_auth *sa = data; uint8_t *aid; if (sa->pending) return __ofono_error_busy(msg); /* get RAND/AUTN and setup handle args */ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &rand, &rlen, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &autn, &alen, DBUS_TYPE_INVALID)) return __ofono_error_invalid_format(msg); if (rlen != 16 || alen != 16) return __ofono_error_invalid_format(msg); sa->pending = g_new0(struct auth_request, 1); sa->pending->msg = dbus_message_ref(msg); sa->pending->rands[0] = rand; sa->pending->num_rands = 1; sa->pending->autn = autn; sa->pending->umts = 1; /* * retrieve session from SIM */ aid = find_aid_by_path(sa->aid_objects, dbus_message_get_path(msg)); sa->pending->session = __ofono_sim_get_session_by_aid(sa->sim, aid); sa->pending->watch_id = __ofono_sim_add_session_watch( sa->pending->session, get_session_cb, sa, NULL); return NULL; } static DBusMessage *get_applications(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_sim_auth *sa = data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter array; DBusMessageIter dict; GSList *aid_iter; reply = dbus_message_new_method_return(msg); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sv}}", &array); /* send empty array */ if (!sa->aid_objects) goto apps_end; aid_iter = sa->aid_objects; while (aid_iter) { struct aid_object *obj = aid_iter->data; switch (obj->type) { case SIM_APP_TYPE_ISIM: dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); append_dict_application(&dict, obj->path, "Ims", "ISim"); dbus_message_iter_close_container(&array, &dict); break; case SIM_APP_TYPE_USIM: dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); append_dict_application(&dict, obj->path, "Umts", "USim"); dbus_message_iter_close_container(&array, &dict); break; default: break; } aid_iter = g_slist_next(aid_iter); } apps_end: dbus_message_iter_close_container(&iter, &array); return reply; } static DBusMessage *get_sim_auth_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_sim_auth *sa = data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; reply = dbus_message_new_method_return(msg); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict); if (sa->nai) ofono_dbus_dict_append(&dict, "NetworkAccessIdentity", DBUS_TYPE_STRING, &sa->nai); dbus_message_iter_close_container(&iter, &dict); return reply; } static DBusMessage *send_properties(DBusConnection *conn, DBusMessage *msg, void *data, const char *type, const char *name) { DBusMessage *reply; DBusMessageIter iter; DBusMessageIter array; reply = dbus_message_new_method_return(msg); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array); ofono_dbus_dict_append(&array, "Type", DBUS_TYPE_STRING, &type); ofono_dbus_dict_append(&array, "Name", DBUS_TYPE_STRING, &name); dbus_message_iter_close_container(&iter, &array); return reply; } static DBusMessage *usim_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { return send_properties(conn, msg, data, "Umts", "USim"); } static DBusMessage *isim_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { return send_properties(conn, msg, data, "Ims", "ISim"); } static DBusMessage *isim_ims_authenticate(DBusConnection *conn, DBusMessage *msg, void *data) { return umts_common(conn, msg, data, SIM_APP_TYPE_ISIM); } static DBusMessage *usim_umts_authenticate(DBusConnection *conn, DBusMessage *msg, void *data) { return umts_common(conn, msg, data, SIM_APP_TYPE_USIM); } static const GDBusMethodTable sim_authentication[] = { { GDBUS_METHOD("GetApplications", NULL, GDBUS_ARGS({"applications", "a{oa{sv}}"}), get_applications) }, { GDBUS_METHOD("GetProperties", NULL, GDBUS_ARGS({"properties", "a{sv}"}), get_sim_auth_properties) }, { } }; static const GDBusMethodTable sim_auth_usim_app[] = { { GDBUS_ASYNC_METHOD("GetProperties", NULL, GDBUS_ARGS({"properties", "a{sv}"}), usim_get_properties) }, { GDBUS_ASYNC_METHOD("GsmAuthenticate", GDBUS_ARGS({"rands", "aay"}), GDBUS_ARGS({"keys", "a{say}"}), usim_gsm_authenticate) }, { GDBUS_ASYNC_METHOD("UmtsAuthenticate", GDBUS_ARGS({"rand", "ay"}, {"autn", "ay"}), GDBUS_ARGS({"return", "a{sv}"}), usim_umts_authenticate) }, { } }; static const GDBusMethodTable sim_auth_isim_app[] = { { GDBUS_ASYNC_METHOD("GetProperties", NULL, GDBUS_ARGS({"properties", "a{sv}"}), isim_get_properties) }, { GDBUS_ASYNC_METHOD("ImsAuthenticate", GDBUS_ARGS({"rand", "ay"}, {"autn", "ay"}), GDBUS_ARGS({"return", "a{sv}"}), isim_ims_authenticate) }, { } }; /* * Build NAI according to TS 23.003. This should only be used as an NAI * if the SimManager interface could not find an NAI from the ISim. */ static char *build_nai(const char *imsi) { char mcc[3]; char mnc[3]; char *nai; memcpy(mcc, imsi, 3); if (strlen(imsi) == 16) { memcpy(mnc, imsi + 3, 3); } else { mnc[0] = '0'; memcpy(mnc + 1, imsi + 3, 2); } nai = g_strdup_printf("%s@ims.mnc%.3s.mcc%.3s.3gppnetwork.org", imsi, mnc, mcc); return nai; } static void sim_auth_register(struct ofono_sim_auth *sa) { DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(sa->atom); struct ofono_modem *modem = __ofono_atom_get_modem(sa->atom); struct ofono_sim *sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, modem); GSList *iter = __ofono_sim_get_aid_list(sim); int ret; sa->sim = sim; if (!iter) { DBG("No AID list"); return; } while (iter) { struct sim_app_record *r = iter->data; struct aid_object *new = g_new0(struct aid_object, 1); new->type = r->type; switch (r->type) { case SIM_APP_TYPE_USIM: new->path = g_new0(char, strlen(path) + 34); ret = sprintf(new->path, "%s/", path); encode_hex_own_buf(r->aid, 16, 0, new->path + ret); g_dbus_register_interface(conn, new->path, OFONO_USIM_APPLICATION_INTERFACE, sim_auth_usim_app, NULL, NULL, sa, NULL); memcpy(new->aid, r->aid, 16); break; case SIM_APP_TYPE_ISIM: new->path = g_new0(char, strlen(path) + 34); ret = sprintf(new->path, "%s/", path); encode_hex_own_buf(r->aid, 16, 0, new->path + ret); g_dbus_register_interface(conn, new->path, OFONO_ISIM_APPLICATION_INTERFACE, sim_auth_isim_app, NULL, NULL, sa, NULL); memcpy(new->aid, r->aid, 16); break; default: DBG("Unknown SIM application '%04x'", r->type); /* * If we get here, the SIM application was not ISIM * or USIM, skip. */ g_free(new); goto loop_end; } sa->aid_objects = g_slist_prepend(sa->aid_objects, new); loop_end: iter = g_slist_next(iter); } /* if IMPI is not available, build the NAI */ if (!__ofono_sim_get_impi(sa->sim)) sa->nai = build_nai(ofono_sim_get_imsi(sa->sim)); else sa->nai = g_strdup(__ofono_sim_get_impi(sa->sim)); g_dbus_register_interface(conn, path, OFONO_SIM_AUTHENTICATION_INTERFACE, sim_authentication, NULL, NULL, sa, NULL); ofono_modem_add_interface(modem, OFONO_SIM_AUTHENTICATION_INTERFACE); __ofono_atom_register(sa->atom, sim_auth_unregister); sa->gsm_access = __ofono_sim_ust_service_available(sim, SIM_UST_SERVICE_GSM_ACCESS); sa->gsm_context = __ofono_sim_ust_service_available(sim, SIM_UST_SERVICE_GSM_SECURITY_CONTEXT); } struct ofono_sim_auth *ofono_sim_auth_create(struct ofono_modem *modem) { struct ofono_sim_auth *sa; sa = g_new0(struct ofono_sim_auth, 1); if (sa == NULL) return NULL; sa->atom = __ofono_modem_add_atom(modem, OFONO_ATOM_TYPE_SIM_AUTH, sim_auth_remove, sa); sim_auth_register(sa); return sa; } void ofono_sim_auth_remove(struct ofono_sim_auth *sa) { __ofono_atom_free(sa->atom); }