blob: 0436e7e9126ba605b0bfe9815718abd1e0ef09a3 [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 "seel.h"
struct iso7816_apdu {
uint8_t class;
uint8_t instruction;
uint8_t param1;
uint8_t param2;
uint8_t body[];
} __attribute__((packed));
struct iso7816_apdu_resp {
uint8_t sw1;
uint8_t sw2;
} __attribute__((packed));
struct seel_apdu {
struct iso7816_apdu *apdu;
size_t length;
} __attribute__((packed));
#define MAX_AID_LENGTH 16
#define MIN_AID_LENGTH 5
#define CLA_CHANNEL_STANDARD 0x0
#define CLA_CHANNEL_EXTENDED 0x1
#define CLA_PROPRIETARY_CMD 0x80
#define CLA_PPS_CMD 0xF0
#define INS_MANAGE_CHANNEL 0x70
#define INS_SELECT_FILE 0xA4
#define INS_GET_GP_DATA 0xCA
#define P1_SELECT_FILE_DF_NAME 0x4
#define APDU_RESP_TRAILER_LENGTH 0x2 /* SW1, SW2 */
#define CLA_CHANNEL_MASK 0xFF
static struct seel_apdu *alloc_apdu(uint8_t class, uint8_t channel,
uint8_t instruction,
uint8_t param1, uint8_t param2,
uint8_t data_length, uint8_t *data,
int resp_length)
{
struct seel_apdu *apdu;
struct iso7816_apdu *iso_apdu;
size_t iso_apdu_length;
uint32_t body_ptr;
if (channel > 3)
return NULL;
apdu = g_try_malloc(sizeof(struct seel_apdu));
if (apdu == NULL)
return apdu;
iso_apdu_length = sizeof(struct iso7816_apdu);
if (data_length > 0)
iso_apdu_length += 1 + data_length;
if (resp_length >= 0)
iso_apdu_length += 1;
apdu->apdu = g_try_malloc(iso_apdu_length);
if (apdu->apdu == NULL) {
g_free(apdu);
return NULL;
}
iso_apdu = apdu->apdu;
iso_apdu->class = class | channel;
iso_apdu->instruction = instruction;
iso_apdu->param1 = param1;
iso_apdu->param2 = param2;
body_ptr = 0;
if (data_length > 0) {
iso_apdu->body[0] = data_length;
memcpy(&iso_apdu->body[1], data, data_length);
body_ptr += data_length + 1;
}
if (resp_length >= 0)
iso_apdu->body[body_ptr] = resp_length;
apdu->length = iso_apdu_length;
return apdu;
}
struct seel_apdu *__seel_apdu_build(uint8_t *apdu, size_t length, uint8_t channel)
{
struct seel_apdu *_apdu;
struct iso7816_apdu *iso_apdu;
_apdu = g_try_malloc(sizeof(struct seel_apdu));
if (!_apdu)
return NULL;
_apdu->apdu = g_try_malloc(length);
if (_apdu->apdu == NULL) {
g_free(_apdu);
return NULL;
}
if (channel > 3) {
DBG("Invalid channel number %d", channel);
channel = 0;
}
iso_apdu = (struct iso7816_apdu *) apdu;
/* We add the channel iff CLA is not PPS */
if ((iso_apdu->class & CLA_PPS_CMD) != CLA_PPS_CMD)
iso_apdu->class |= channel;
_apdu->length = length;
memcpy(_apdu->apdu, apdu, length);
return _apdu;
}
void __seel_apdu_dump(uint8_t *apdu, size_t length)
{
size_t i;
char *str;
str = g_try_malloc0((3 * length) + 1);
if (str == NULL)
return;
for (i = 0; i < length; i++)
sprintf(str + (3 * i), "%02X ", apdu[i]);
str[3 * length] = 0;
DBG("[%zd] %s", length, str);
g_free(str);
}
void __seel_apdu_free(struct seel_apdu *apdu)
{
g_free(apdu->apdu);
g_free(apdu);
}
size_t __seel_apdu_length(struct seel_apdu *apdu)
{
return apdu->length;
}
uint8_t *__seel_apdu_data(struct seel_apdu *apdu)
{
return (uint8_t *) apdu->apdu;
}
struct seel_apdu *__seel_apdu_open_logical_channel(void)
{
return alloc_apdu(CLA_CHANNEL_STANDARD, 0, INS_MANAGE_CHANNEL, 0, 0,
0, NULL, 1);
}
struct seel_apdu *__seel_apdu_close_logical_channel(uint8_t channel)
{
DBG("%d", channel);
return alloc_apdu(CLA_CHANNEL_STANDARD, 0, INS_MANAGE_CHANNEL, 0x80,
channel, 0, NULL, -1);
}
struct seel_apdu *__seel_apdu_select_aid(uint8_t channel,
uint8_t *aid, size_t aid_length)
{
DBG("%zd", aid_length);
if (aid_length < MIN_AID_LENGTH ||
aid_length > MAX_AID_LENGTH)
return NULL;
return alloc_apdu(CLA_CHANNEL_STANDARD, channel, INS_SELECT_FILE,
P1_SELECT_FILE_DF_NAME, 0,
aid_length, aid, -1);
}
struct seel_apdu *__seel_apdu_get_all_gp_data(void)
{
DBG("");
return alloc_apdu(CLA_PROPRIETARY_CMD, 0, INS_GET_GP_DATA,
0xFF, 0x40, 0, NULL, 0);
}
struct seel_apdu *__seel_apdu_get_next_gp_data(size_t length)
{
DBG("");
return alloc_apdu(CLA_PROPRIETARY_CMD, 0, INS_GET_GP_DATA,
0xFF, 0x60, 0, NULL, length);
}
struct seel_apdu *__seel_apdu_get_refresh_gp_data(void)
{
DBG("");
return alloc_apdu(CLA_PROPRIETARY_CMD, 0, INS_GET_GP_DATA,
0xDF, 0x20, 0, NULL, 0xB);
}
static int apdu_trailer_status(struct iso7816_apdu_resp *trailer)
{
DBG("SW1 0x%x SW2 0x%x", trailer->sw1, trailer->sw2);
switch (trailer->sw1) {
case 0x90:
if (trailer->sw2 == 0)
return 0;
default:
return -EIO;
}
}
int __seel_apdu_resp_status(uint8_t *apdu, size_t apdu_length)
{
struct iso7816_apdu_resp *resp;
if (apdu_length < APDU_RESP_TRAILER_LENGTH)
return -EINVAL;
__seel_apdu_dump(apdu, apdu_length);
resp = (struct iso7816_apdu_resp *)(apdu + apdu_length - APDU_RESP_TRAILER_LENGTH);
return apdu_trailer_status(resp);
}