blob: c1561abae8c147cb47858b0d7736e112ec085c78 [file] [log] [blame]
/*
*
* seeld - Secure Element Manager
*
* Copyright (C) 2013 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.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <gdbus.h>
#include "driver.h"
#include "seel.h"
#define MAX_AID_LEN 16
#define MIN_AID_LEN 5
#define APP_HASH_LEN 20
#define GP_AID_LEN 9
uint8_t gp_aid[] = {0xA0, 0x00, 0x00, 0x01,
0x51, 0x41, 0x43, 0x4C, 0x00 };
/* Data Object Tags */
#define AID_REF_DO_ALL 0xC0
#define HASH_REF_DO 0xC1
#define AID_REF_DO 0x4F
#define REF_DO 0xE1
#define REF_AR_DO 0xE2
#define AR_DO 0xE3
#define APDU_AR_DO 0xD0
#define NFC_AR_DO 0xD1
#define APDU_STATUS_LEN 2
#define GET_ALL_DATA_CMD_LEN 2
#define GET_REFRESH_DATA_CMD_LEN 2
#define GET_REFRESH_DATA_TAG_LEN 1
#define GET_REFRESH_TAG_LEN 8
/* if ((APDU_header & rule->mask) == rule->header) then APDU is allowed */
struct seel_ace_apdu_rule {
uint32_t header;
uint32_t mask;
};
struct seel_ace_rule {
uint8_t aid[MAX_AID_LEN];
size_t aid_len;
uint8_t hash[APP_HASH_LEN];
size_t hash_len;
uint8_t *apdu_rules;
size_t apdu_rules_len;
bool nfc_rule;
};
struct seel_ace {
struct seel_se *se;
size_t rules_length;
size_t current_rules_length;
uint8_t *rules_payload;
uint8_t rules_tag[8];
GSList *rules;
};
GHashTable *ace_hash;
static void free_rule(gpointer data)
{
struct seel_ace_rule *rule = data;
DBG("%p", rule);
g_free(rule->apdu_rules);
g_free(rule);
}
static void free_ace(gpointer data)
{
struct seel_ace *ace = data;
DBG("%p %p", ace, ace->rules);
g_slist_free_full(ace->rules, free_rule);
g_free(ace);
}
static void dump_rule(gpointer data, gpointer user_data)
{
struct seel_ace_rule *rule = data;
struct seel_ace_apdu_rule *apdu_rule;
char aid[3 * MAX_AID_LEN + 1];
char hash[3 * APP_HASH_LEN + 1];
size_t i;
DBG("ACE Rule:");
if (rule->aid_len == 0) {
DBG(" Hash: All SE applications");
} else {
for (i = 0; i < rule->aid_len; i++)
sprintf(aid + (3 * i), "%02X ", rule->aid[i]);
aid[3 * i] = 0;
DBG(" AID [%zd]: %s", rule->aid_len, aid);
}
if (rule->hash_len == 0) {
DBG(" Hash: All host applications");
} else {
for (i = 0; i < rule->hash_len; i++)
sprintf(hash + (3 * i), "%02X ", rule->hash[i]);
hash[3 * i] = 0;
DBG(" Hash [%zd]: %s", rule->hash_len, hash);
}
if (rule->apdu_rules_len == 1) {
DBG(" APDU: %s", rule->apdu_rules[0] ? "Always" : "Never");
} else {
uint8_t *header, *mask;
size_t n_rules;
apdu_rule = (struct seel_ace_apdu_rule *)rule->apdu_rules;
n_rules = rule->apdu_rules_len /
sizeof(struct seel_ace_apdu_rule);
DBG(" APDU rules (%zd)", n_rules);
for (i = 0; i < n_rules; i++) {
header = (uint8_t *)&apdu_rule->header;
mask = (uint8_t *)&apdu_rule->mask;
DBG(" header 0x%02x:0x%02x:0x%02x:0x%02x "
"mask 0x%02x:0x%02x:0x%02x:0x%02x",
header[0], header[1], header[2], header[3],
mask[0], mask[1], mask[2], mask[3]);
apdu_rule++;
}
}
DBG(" NFC: %s", rule->nfc_rule ? "Always" : "Never");
}
static void dump_ace(struct seel_ace *ace)
{
DBG("ACE Tag [0x%02x:0x%02x:0x%02x:0x%02x:0x%02x:0x%02x:0x%02x:0x%02x]",
ace->rules_tag[0], ace->rules_tag[1], ace->rules_tag[2], ace->rules_tag[3],
ace->rules_tag[4], ace->rules_tag[5], ace->rules_tag[6], ace->rules_tag[7]);
g_slist_foreach(ace->rules, dump_rule, NULL);
}
static int build_ref(struct seel_ace_rule *ace_rule,
uint8_t *rule, size_t rule_length)
{
uint8_t *rule_ptr;
size_t remaining, do_length;
remaining = rule_length;
rule_ptr = rule;
while (remaining) {
switch (rule_ptr[0]) {
case AID_REF_DO:
do_length = rule_ptr[1];
DBG("AID_REF_DO %zd", do_length);
rule_ptr += 2;
if (remaining < do_length)
return -EINVAL;
if (do_length > MAX_AID_LEN)
return -EINVAL;
memcpy(ace_rule->aid, rule_ptr, do_length);
ace_rule->aid_len = do_length;
remaining -= do_length + 2;
rule_ptr += do_length;
break;
case HASH_REF_DO:
do_length = rule_ptr[1];
DBG("HASH_REF_DO %zd", do_length);
rule_ptr += 2;
if (remaining < do_length)
return -EINVAL;
if (do_length > APP_HASH_LEN)
return -EINVAL;
memcpy(ace_rule->hash, rule_ptr, do_length);
ace_rule->hash_len = do_length;
remaining -= do_length + 2;
rule_ptr += do_length;
break;
default:
return -EINVAL;
}
}
return 0;
}
static int build_ar(struct seel_ace_rule *ace_rule,
uint8_t *rule, size_t rule_length)
{
uint8_t *rule_ptr;
size_t remaining, do_length;
remaining = rule_length;
rule_ptr = rule;
while (remaining) {
switch (rule_ptr[0]) {
case APDU_AR_DO:
do_length = rule_ptr[1];
DBG("APDU_AR_DO %zd", do_length);
rule_ptr += 2;
if (remaining < do_length)
return -EINVAL;
ace_rule->apdu_rules = g_try_malloc0(do_length);
if (!ace_rule->apdu_rules)
return -ENOMEM;
memcpy(ace_rule->apdu_rules, rule_ptr, do_length);
ace_rule->apdu_rules_len = do_length;
remaining -= do_length + 2;
rule_ptr += do_length;
break;
case NFC_AR_DO:
do_length = rule_ptr[1];
DBG("NFC_AR_DO %zd", do_length);
rule_ptr += 2;
if (do_length != 1)
return -EINVAL;
ace_rule->nfc_rule = !!rule_ptr[0];
remaining -= do_length + 2;
rule_ptr += do_length;
break;
default:
return -EINVAL;
}
}
return 0;
}
static struct seel_ace_rule *build_rule(struct seel_ace *ace,
uint8_t *rule, size_t rule_length)
{
struct seel_ace_rule *ace_rule;
uint8_t *rule_ptr;
size_t remaining, do_length;
int err;
DBG("");
ace_rule = g_try_malloc0(sizeof(struct seel_ace_rule));
if (!ace_rule)
return NULL;
remaining = rule_length;
rule_ptr = rule;
while (remaining) {
switch (rule_ptr[0]) {
case REF_DO:
do_length = rule_ptr[1];
DBG("REF_DO %zd", do_length);
rule_ptr += 2;
err = build_ref(ace_rule, rule_ptr, do_length);
if (err)
goto error;
remaining -= do_length + 2;
rule_ptr += do_length;
break;
case AR_DO:
do_length = rule_ptr[1];
DBG("AR_DO %zd", do_length);
rule_ptr += 2;
err = build_ar(ace_rule, rule_ptr, do_length);
if (err)
goto error;
remaining -= do_length + 2;
rule_ptr += do_length;
break;
default:
goto error;
}
}
return ace_rule;
error:
free_rule(ace_rule);
return NULL;
}
static int build_ace_rules(struct seel_ace *ace,
uint8_t *rules, size_t rules_length)
{
struct seel_ace_rule *rule;
uint8_t *rule_ptr;
size_t remaining = rules_length, ref_ar_do_length;
int err;
DBG("");
err = 0;
remaining = rules_length;
rule_ptr = rules;
while (remaining) {
if (rule_ptr[0] != REF_AR_DO) {
rule_ptr++;
remaining--;
continue;
}
ref_ar_do_length = rule_ptr[1];
/* Tag + Length + value length */
remaining -= ref_ar_do_length + 2;
rule_ptr += 2;
DBG("REF_AR_DO %zd bytes", ref_ar_do_length);
rule = build_rule(ace, rule_ptr, ref_ar_do_length);
if (!rule) {
err = -EINVAL;
break;
}
ace->rules = g_slist_append(ace->rules, rule);
rule_ptr += ref_ar_do_length;
}
return err;
}
static int ace_rule_length(uint8_t *apdu, size_t apdu_length,
size_t *length_length)
{
size_t length;
if (apdu_length < 3)
return -EINVAL;
length = apdu[2];
*length_length = 1;
/*
* BER: If length bit 8 is one, bit [0..7] is the number
* of bytes used for encoding the length.
*/
if (length & 0x80) {
size_t _length = length & 0x7f, i, base;
DBG("%zd", _length);
if (apdu_length < 3 + _length)
return -EINVAL;
length = 0;
base = 1 << 8 * _length;
for (i = 0; i < _length; i++) {
base >>= 8;
length += apdu[3 + i] * base;
}
*length_length = _length + 1;
}
DBG("length 0x%zx", length);
return length;
}
static void get_next_gp_data(struct seel_ace *ace);
static void get_next_gp_data_cb(void *context,
uint8_t *apdu, size_t apdu_length,
int err)
{
struct seel_ace *ace = context;
size_t payload_length;
DBG("Current %zd Total %zd Got %zd",
ace->current_rules_length, ace->rules_length, apdu_length);
if (err)
goto out;
payload_length = apdu_length - APDU_STATUS_LEN;
if (ace->current_rules_length + payload_length > ace->rules_length)
goto out;
memcpy(ace->rules_payload + ace->current_rules_length,
apdu, payload_length);
ace->current_rules_length += apdu_length - APDU_STATUS_LEN;
if (ace->current_rules_length < ace->rules_length)
return get_next_gp_data(ace);
if (build_ace_rules(ace, ace->rules_payload, ace->rules_length))
goto out;
dump_ace(ace);
g_hash_table_replace(ace_hash, ace->se, ace);
out:
g_free(ace->rules_payload);
return;
}
static void get_next_gp_data(struct seel_ace *ace)
{
struct seel_apdu *get_next_gp_data;
size_t req_length;
int err;
if (ace->rules_length - ace->current_rules_length < 0x100)
req_length = ace->rules_length - ace->current_rules_length;
else
req_length = 0;
get_next_gp_data = __seel_apdu_get_next_gp_data(req_length);
if (!get_next_gp_data) {
g_free(ace->rules_payload);
return;
}
err = __seel_se_queue_io(ace->se, get_next_gp_data,
get_next_gp_data_cb, ace);
if (err < 0) {
near_error("GET NEXT ALL err %d", err);
return;
}
return;
}
static void get_all_gp_data_cb(void *context,
uint8_t *apdu, size_t apdu_length,
int err)
{
struct seel_ace *ace = context;
size_t length_length, payload_length;
int rule_length;
DBG("");
if (err)
return;
rule_length = ace_rule_length(apdu, apdu_length, &length_length);
if (rule_length < 0)
return;
if (apdu_length > GET_ALL_DATA_CMD_LEN + length_length +
rule_length + APDU_STATUS_LEN)
return;
payload_length = apdu_length - GET_ALL_DATA_CMD_LEN -
length_length - APDU_STATUS_LEN;
DBG("Received %zd bytes of payload", payload_length);
if (payload_length < (size_t)rule_length) {
ace->rules_length = rule_length;
ace->current_rules_length = payload_length;
ace->rules_payload = g_try_malloc0(rule_length);
if (!ace->rules_payload)
return;
memcpy(ace->rules_payload,
apdu + GET_ALL_DATA_CMD_LEN + length_length,
payload_length);
return get_next_gp_data(ace);
}
if (build_ace_rules(ace, apdu + GET_ALL_DATA_CMD_LEN + length_length,
payload_length))
return;
dump_ace(ace);
g_hash_table_replace(ace_hash, ace->se, ace);
return;
}
static void get_refresh_gp_data_cb(void *context,
uint8_t *apdu, size_t apdu_length,
int err)
{
struct seel_se *se = context;
struct seel_ace *ace;
struct seel_apdu *get_all_gp_data;
DBG("");
if (err)
return;
if (apdu_length != GET_REFRESH_DATA_CMD_LEN + GET_REFRESH_DATA_TAG_LEN
+ GET_REFRESH_TAG_LEN + APDU_STATUS_LEN)
return;
if (apdu[0] != 0xDF || apdu[1] != 0x20 || apdu[2] != 0x08)
return;
ace = g_try_malloc0(sizeof(struct seel_ace));
if (!ace)
return;
ace->se = se;
memcpy(ace->rules_tag, apdu + GET_REFRESH_DATA_CMD_LEN
+ GET_REFRESH_DATA_TAG_LEN, GET_REFRESH_TAG_LEN);
get_all_gp_data = __seel_apdu_get_all_gp_data();
if (!get_all_gp_data)
return;
err = __seel_se_queue_io(se, get_all_gp_data, get_all_gp_data_cb, ace);
if (err < 0) {
near_error("GET DATA ALL err %d", err);
return;
}
return ;
}
static void select_gp_aid_cb(void *context,
uint8_t *apdu, size_t apdu_length,
int err)
{
struct seel_se *se = context;
struct seel_apdu *get_refresh_gp_data;
DBG("");
if (err)
return;
get_refresh_gp_data = __seel_apdu_get_refresh_gp_data();
if (!get_refresh_gp_data)
return;
err = __seel_se_queue_io(se, get_refresh_gp_data, get_refresh_gp_data_cb, se);
if (err < 0) {
near_error("GET REFRESH DATA err %d", err);
return;
}
return ;
}
gboolean __seel_ace_add(gpointer user_data)
{
struct seel_se *se = user_data;
struct seel_apdu *select_gp_aid;
int err;
DBG("");
select_gp_aid = __seel_apdu_select_aid(0, gp_aid, GP_AID_LEN);
if (!select_gp_aid)
return FALSE;
/* Send the GP AID selection APDU */
err = __seel_se_queue_io(se, select_gp_aid, select_gp_aid_cb, se);
if (err < 0) {
near_error("GP AID err %d", err);
return FALSE;
}
return FALSE;
}
int __seel_ace_remove(struct seel_se *se)
{
DBG("%p", se);
if (!g_hash_table_remove(ace_hash, se))
return -ENODEV;
return 0;
}
static struct seel_ace_rule *find_specific_rule(struct seel_ace *ace,
uint8_t *aid, size_t aid_len,
uint8_t *hash)
{
GSList *list;
DBG("%zd", aid_len);
if (!hash || !aid)
return false;
for (list = ace->rules; list; list = list->next) {
struct seel_ace_rule *rule = list->data;
if (rule->aid_len != aid_len)
continue;
if (rule->hash_len != APP_HASH_LEN)
continue;
if (!memcmp(rule->aid, aid, aid_len) &&
!memcmp(rule->hash, hash, APP_HASH_LEN)) {
dump_rule(rule, NULL);
return rule;
}
}
return NULL;
}
static bool find_specific_rule_for_aid(struct seel_ace *ace,
uint8_t *aid, size_t aid_len)
{
GSList *list;
DBG("%zd", aid_len);
if (!aid)
return false;
for (list = ace->rules; list; list = list->next) {
struct seel_ace_rule *rule = list->data;
if (rule->aid_len != aid_len)
continue;
if (rule->hash_len != APP_HASH_LEN)
continue;
if (!memcmp(rule->aid, aid, aid_len)) {
dump_rule(rule, NULL);
return true;
}
}
return false;
}
static struct seel_ace_rule *find_generic_rule_for_aid(struct seel_ace *ace,
uint8_t *aid, size_t aid_len)
{
GSList *list;
DBG("%zd", aid_len);
if (!aid)
return false;
for (list = ace->rules; list; list = list->next) {
struct seel_ace_rule *rule = list->data;
if (rule->aid_len != aid_len)
continue;
/* This is a specific rule */
if (rule->hash_len)
continue;
if (!memcmp(rule->aid, aid, aid_len)) {
dump_rule(rule, NULL);
return rule;
}
}
return NULL;
}
static struct seel_ace_rule *find_generic_rule_for_hash(struct seel_ace *ace,
uint8_t *hash)
{
GSList *list;
DBG("");
if (!hash)
return false;
for (list = ace->rules; list; list = list->next) {
struct seel_ace_rule *rule = list->data;
/* This is a generic hash rule */
if (rule->hash_len != APP_HASH_LEN)
continue;
/* This is a specific AID rule */
if (rule->aid_len)
continue;
if (!memcmp(rule->hash, hash, APP_HASH_LEN)) {
dump_rule(rule, NULL);
return rule;
}
}
return NULL;
}
static struct seel_ace_rule *find_generic_rule(struct seel_ace *ace)
{
GSList *list;
DBG("");
for (list = ace->rules; list; list = list->next) {
struct seel_ace_rule *rule = list->data;
if (rule->hash_len || rule->aid_len)
continue;
dump_rule(rule, NULL);
return rule;
}
return NULL;
}
static bool apdu_allowed(struct seel_ace_rule *rule,
uint8_t *apdu, size_t apdu_len)
{
size_t i, n_rules;
uint32_t apdu_header;
struct seel_ace_apdu_rule *apdu_rule;
if (rule->apdu_rules_len == 1)
return rule->apdu_rules[0] ? true : false;
n_rules = rule->apdu_rules_len /
sizeof(struct seel_ace_apdu_rule);
apdu_header = *((uint32_t *) apdu);
apdu_rule = (struct seel_ace_apdu_rule *)rule->apdu_rules;
for (i = 0; i < n_rules; i++) {
if ((apdu_header & apdu_rule->mask) == apdu_rule->header)
return true;
apdu_rule++;
}
return false;
}
bool __seel_ace_apdu_allowed(struct seel_channel *channel, uint8_t *hash,
uint8_t *apdu, size_t apdu_len)
{
struct seel_se *se;
struct seel_ace *ace;
struct seel_ace_rule *rule;
uint8_t *aid;
size_t aid_len;
DBG("%zd", apdu_len);
/* XXX Do we need to do some filtering on the basic channel ?*/
if (__seel_channel_is_basic(channel))
return true;
se = __seel_channel_get_se(channel);
if (!se)
return false;
ace = g_hash_table_lookup(ace_hash, se);
if (!ace)
return false;
if (!ace->rules)
return false;
/* Ref. GP SE Access Control specification, Chapter 4: Device Interface
* If the secure element is a uicc, a device application can access an SE
* application in all of the following case:
* - The ARA-M is accessible and provides a rule which explicitly allows the
* access.
* - The ARA-M is not accessible but the ARF contains a rule which allows the
* access.
*
* If the secure element is not a uicc, a device application can access an SE
* application in all of the following case:
* - The ARA-M is not accessible on the SE (e.g not installed, not selectable,
* or locked).
* - The ARA-M is accessible and provides a rule which explicitly allows the
* access.
*/
aid = __seel_channel_get_aid(channel, &aid_len);
if (!aid)
return __seel_se_get_type(se) != SEEL_SE_UICC;
/* a) Try to find a specific rule */
rule = find_specific_rule(ace, aid, aid_len, hash);
if (rule)
return apdu_allowed(rule, apdu, apdu_len) ? true : false;
/*
* a') Try to find a specific rule for another hash
* If there is such a rule, then access is denied for the
* current hash: Specific rule precedence over generic ones.
*/
if (find_specific_rule_for_aid(ace, aid, aid_len))
return false;
/* b) Search for a generic rule for this specific AID */
rule = find_generic_rule_for_aid(ace, aid, aid_len);
if (rule)
return apdu_allowed(rule, apdu, apdu_len) ? true : false;
/* c) Search for a generic rule for this specific hash */
rule = find_generic_rule_for_hash(ace, hash);
if (rule)
return apdu_allowed(rule, apdu, apdu_len) ? true : false;
/* d) Search for a generic rule: All apps, all AIDs */
rule = find_generic_rule(ace);
if (rule)
return apdu_allowed(rule, apdu, apdu_len) ? true : false;
return false;
}
int __seel_ace_init(void)
{
DBG("");
ace_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, free_ace);
return 0;
}
void __seel_ace_cleanup(void)
{
DBG("");
g_hash_table_destroy(ace_hash);
ace_hash = NULL;
}