view uicc/select.c @ 45:9eb5460f51a6

main tools: support both pcsc and serial back ends
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 21 Mar 2021 01:56:49 +0000
parents b70d35f5476f
children 0e46bbb801e0
line wrap: on
line source

#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include "simresp.h"

u_char std_aid_usim[7] = {0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02};
u_char std_aid_isim[7] = {0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x04};

unsigned last_sel_file_record_len;

elem_select_op(file_id)
	unsigned file_id;
{
	u_char cmd[7];
	int rc;

	last_sel_file_record_len = 0;
	/* SELECT command APDU */
	cmd[0] = 0x00;
	cmd[1] = 0xA4;
	cmd[2] = 0x00;
	cmd[3] = 0x04;
	cmd[4] = 2;
	cmd[5] = file_id >> 8;
	cmd[6] = file_id;
	return apdu_exchange(cmd, 7);
}

select_op(file_id)
	unsigned file_id;
{
	u_char cmd[7];
	int rc;
	unsigned expect_resp_len;

	last_sel_file_record_len = 0;
	/* SELECT command APDU */
	cmd[0] = 0x00;
	cmd[1] = 0xA4;
	cmd[2] = 0x00;
	cmd[3] = 0x04;
	cmd[4] = 2;
	cmd[5] = file_id >> 8;
	cmd[6] = file_id;
	rc = apdu_exchange(cmd, 7);
	if (rc < 0)
		return(rc);
	if ((sim_resp_sw & 0xFF00) != 0x6100) {
		fprintf(stderr,
		"error or unexpected SW response to SELECT of 0x%04X: %04X\n",
			file_id, sim_resp_sw);
		return(-1);
	}
	expect_resp_len = sim_resp_sw & 0xFF;
	/* GET RESPONSE follow-up */
	cmd[1] = 0xC0;
	cmd[2] = 0;
	cmd[3] = 0;
	cmd[4] = expect_resp_len;
	rc = apdu_exchange(cmd, 5);
	if (rc < 0)
		return(rc);
	if (sim_resp_sw != 0x9000) {
		fprintf(stderr,
			"bad SW resp to GET RESPONSE after SELECT: %04X\n",
			sim_resp_sw);
		return(-1);
	}
	if (sim_resp_data_len != expect_resp_len) {
		fprintf(stderr,
	"error: GET RESPONSE after SELECT returned %u bytes, expected %u\n",
			sim_resp_data_len, expect_resp_len);
		return(-1);
	}
	return(0);
}

select_aid_op(aid, aid_len)
	u_char *aid;
	unsigned aid_len;
{
	u_char cmd[21];
	int rc;
	unsigned expect_resp_len;

	last_sel_file_record_len = 0;
	/* SELECT command APDU */
	cmd[0] = 0x00;
	cmd[1] = 0xA4;
	cmd[2] = 0x04;
	cmd[3] = 0x04;
	cmd[4] = aid_len;
	bcopy(aid, cmd + 5, aid_len);
	rc = apdu_exchange(cmd, aid_len + 5);
	if (rc < 0)
		return(rc);
	if ((sim_resp_sw & 0xFF00) != 0x6100) {
		fprintf(stderr,
		"error or unexpected SW response to SELECT by AID: %04X\n",
			sim_resp_sw);
		return(-1);
	}
	expect_resp_len = sim_resp_sw & 0xFF;
	/* GET RESPONSE follow-up */
	cmd[1] = 0xC0;
	cmd[2] = 0;
	cmd[3] = 0;
	cmd[4] = expect_resp_len;
	rc = apdu_exchange(cmd, 5);
	if (rc < 0)
		return(rc);
	if (sim_resp_sw != 0x9000) {
		fprintf(stderr,
			"bad SW resp to GET RESPONSE after SELECT: %04X\n",
			sim_resp_sw);
		return(-1);
	}
	if (sim_resp_data_len != expect_resp_len) {
		fprintf(stderr,
	"error: GET RESPONSE after SELECT returned %u bytes, expected %u\n",
			sim_resp_data_len, expect_resp_len);
		return(-1);
	}
	return(0);
}

select_resp_header_check(ret_offset, ret_length)
	unsigned *ret_offset, *ret_length;
{
	unsigned offset, len;

	if (sim_resp_data_len < 2) {
tooshort:	fprintf(stderr, "error: SELECT response is too short\n");
		return(-1);
	}
	if (sim_resp_data[0] != 0x62) {
		fprintf(stderr, "error: SELECT response first byte != 0x62\n");
		return(-1);
	}
	len = sim_resp_data[1];
	if (len <= 0x7F) {
		offset = 2;
return_check:	if (offset + len > sim_resp_data_len)
			goto tooshort;
		if (ret_offset)
			*ret_offset = offset;
		if (ret_length)
			*ret_length = len;
		return(0);
	}
	if (len != 0x81) {
		fprintf(stderr, "SELECT response: first length byte is bad\n");
		return(-1);
	}
	if (sim_resp_data_len < 3)
		goto tooshort;
	len = sim_resp_data[2];
	offset = 3;
	goto return_check;
}

static void
check_for_record_struct(tlv)
	u_char *tlv;
{
	unsigned reclen;

	if (tlv[1] != 5)
		return;
	if (tlv[2] & 0x80)
		return;
	if ((tlv[2] & 0x38) == 0x38)
		return;
	if ((tlv[2] & 0x03) != 0x02)
		return;
	reclen = (tlv[4] << 8) | tlv[5];
	if (reclen < 1 || reclen > 255)
		return;
	last_sel_file_record_len = reclen;
}

parse_and_display_select_response(outf)
	FILE *outf;
{
	unsigned offset, totlen, reclen, n;
	u_char *dp, *endp;
	int rc;

	rc = select_resp_header_check(&offset, &totlen);
	if (rc < 0)
		return(rc);
	dp = sim_resp_data + offset;
	endp = sim_resp_data + offset + totlen;
	while (dp < endp) {
		if (endp - dp < 2) {
trunc_error:		fprintf(stderr,
			"error: truncated TLV record in SELECT response\n");
			return(-1);
		}
		if ((dp[0] & 0x1F) == 0x1F) {
			fprintf(stderr,
		"error: extended tag not supported in SELECT response\n");
			return(-1);
		}
		if (dp[1] & 0x80) {
			fprintf(stderr,
		"error: extended length not supported in SELECT response\n");
			return(-1);
		}
		reclen = dp[1] + 2;
		if (endp - dp < reclen)
			goto trunc_error;
		if (dp[0] == 0x82)
			check_for_record_struct(dp);
		for (n = 0; n < reclen; n++) {
			if (n)
				putc(' ', outf);
			fprintf(outf, "%02X", *dp++);
		}
		putc('\n', outf);
	}
	return(0);
}

cmd_select(argc, argv, outf)
	char **argv;
	FILE *outf;
{
	int file_id, rc;

	if (isxdigit(argv[1][0]) && isxdigit(argv[1][1]) &&
	    isxdigit(argv[1][2]) && isxdigit(argv[1][3]) && !argv[1][4])
		file_id = strtoul(argv[1], 0, 16);
	else
		file_id = find_symbolic_file_name(argv[1]);
	if (file_id < 0) {
		fprintf(stderr,
"error: file ID argument is not a hex value or a recognized symbolic name\n");
		return(-1);
	}
	rc = select_op(file_id);
	if (rc < 0)
		return(rc);
	return parse_and_display_select_response(outf);
}

cmd_select_aid(argc, argv, outf)
	char **argv;
	FILE *outf;
{
	u_char aid[16];
	unsigned aid_len;
	int rc;

	rc = decode_hex_data_from_string(argv[1], aid, 1, 16);
	if (rc < 0)
		return(rc);
	aid_len = rc;
	rc = select_aid_op(aid, aid_len);
	if (rc < 0)
		return(rc);
	return parse_and_display_select_response(outf);
}

cmd_select_usim(argc, argv, outf)
	char **argv;
	FILE *outf;
{
	int rc;

	rc = select_aid_op(std_aid_usim, 7);
	if (rc < 0)
		return(rc);
	return parse_and_display_select_response(outf);
}

cmd_select_isim(argc, argv, outf)
	char **argv;
	FILE *outf;
{
	int rc;

	rc = select_aid_op(std_aid_isim, 7);
	if (rc < 0)
		return(rc);
	return parse_and_display_select_response(outf);
}

u_char *
extract_select_resp_tag(sought_tag)
	unsigned sought_tag;
{
	unsigned offset, totlen, reclen;
	u_char *dp, *endp;
	int rc;

	rc = select_resp_header_check(&offset, &totlen);
	if (rc < 0)
		return(0);
	dp = sim_resp_data + offset;
	endp = sim_resp_data + offset + totlen;
	while (dp < endp) {
		if (endp - dp < 2) {
trunc_error:		fprintf(stderr,
			"error: truncated TLV record in SELECT response\n");
			return(0);
		}
		if ((dp[0] & 0x1F) == 0x1F) {
			fprintf(stderr,
		"error: extended tag not supported in SELECT response\n");
			return(0);
		}
		if (dp[1] & 0x80) {
			fprintf(stderr,
		"error: extended length not supported in SELECT response\n");
			return(0);
		}
		reclen = dp[1] + 2;
		if (endp - dp < reclen)
			goto trunc_error;
		if (dp[0] == sought_tag)
			return(dp);
		dp += reclen;
	}
	fprintf(stderr, "error: tag 0x%02X not found in SELECT response\n",
		sought_tag);
	return(0);
}

select_resp_get_transparent(lenp)
	unsigned *lenp;
{
	u_char *tlv;

	tlv = extract_select_resp_tag(0x82);
	if (!tlv)
		return(-1);
	if (tlv[1] != 2) {
bad_file_desc:	fprintf(stderr, "error: file type is not transparent EF\n");
		return(-1);
	}
	if (tlv[2] & 0x80)
		goto bad_file_desc;
	if ((tlv[2] & 0x38) == 0x38)
		goto bad_file_desc;
	if ((tlv[2] & 0x07) != 0x01)
		goto bad_file_desc;
	tlv = extract_select_resp_tag(0x80);
	if (!tlv)
		return(-1);
	if (tlv[1] != 2) {
		fprintf(stderr,
			"error: file size TLV element has wrong length\n");
		return(-1);
	}
	if (lenp)
		*lenp = (tlv[2] << 8) | tlv[3];
	return(0);
}

select_resp_get_linear_fixed(rec_len_ret, rec_count_ret)
	unsigned *rec_len_ret, *rec_count_ret;
{
	u_char *tlv;
	unsigned reclen;

	tlv = extract_select_resp_tag(0x82);
	if (!tlv)
		return(-1);
	if (tlv[1] != 5) {
bad_file_desc:	fprintf(stderr, "error: file type is not linear fixed EF\n");
		return(-1);
	}
	if (tlv[2] & 0x80)
		goto bad_file_desc;
	if ((tlv[2] & 0x38) == 0x38)
		goto bad_file_desc;
	if ((tlv[2] & 0x07) != 0x02)
		goto bad_file_desc;
	reclen = (tlv[4] << 8) | tlv[5];
	if (reclen < 1 || reclen > 255) {
		fprintf(stderr,
			"error: SELECT response gives invalid record length\n");
		return(-1);
	}
	if (rec_len_ret)
		*rec_len_ret = reclen;
	if (rec_count_ret)
		*rec_count_ret = tlv[6];
	return(0);
}