view uicc/select.c @ 74:8562d8508cf2

grcard2-set-{adm,super}-hex commands implemented It appears that GrcardSIM2 cards allow arbitrary 64-bit keys for ADM and SUPER ADM, not necessarily consisting of ASCII digits like the specs require for standard PIN and PUK, and pySim-prog.py in fact sets the ADM key to 4444444444444444 in hex by default, which is not an ASCII digit string. If the cards allow such keys, we need to support them too.
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 16 Feb 2021 04:10:36 +0000
parents 1b1468869ccf
children 58406ead2497
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;

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()
{
	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)
				putchar(' ');
			printf("%02X", *dp++);
		}
		putchar('\n');
	}
	return(0);
}

cmd_select(argc, argv)
	char **argv;
{
	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();
}

cmd_select_aid(argc, argv)
	char **argv;
{
	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();
}

cmd_select_usim()
{
	int rc;

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

cmd_select_isim()
{
	int rc;

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

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);
}