view uicc/select.c @ 93:6041c601304d

fcsim1-mkprov: revert OTA key addition It appears that GrcardSIM2 cards (which is what we got for FCSIM1) do not support OTA after all, contrary to what we were previously led to believe by some tech support emails from Grcard - apparently those support emails and OTA descriptions referred to some other card model(s).
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 21 Apr 2021 05:38:39 +0000
parents db131929ee96
children
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"
#include "efstruct.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);
}

select_resp_get_ef_struct(efs)
	struct ef_struct *efs;
{
	u_char *tlv;

	tlv = extract_select_resp_tag(0x82);
	if (!tlv)
		return(-1);
	if (tlv[1] < 2) {
bad_file_desc:	fprintf(stderr, "error: unable to figure out file structure\n");
		return(-1);
	}
	if (tlv[2] & 0x80)
		goto bad_file_desc;
	if ((tlv[2] & 0x38) == 0x38)
		goto bad_file_desc;
	efs->structure = tlv[2] & 0x07;
	switch (efs->structure) {
	case 0x01:
		if (tlv[1] != 2) {
			fprintf(stderr,
		"error: file descriptor TLV element has wrong length\n");
			return(-1);
		}
		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);
		}
		efs->total_size = (tlv[2] << 8) | tlv[3];
		if (efs->total_size > 0x8000) {
			fprintf(stderr,
		"error: transparent file size exceeds UICC protocol limit\n");
			return(-1);
		}
		return(0);
	case 0x02:
	case 0x06:
		if (tlv[1] != 5) {
			fprintf(stderr,
		"error: file descriptor TLV element has wrong length\n");
			return(-1);
		}
		efs->record_len = (tlv[4] << 8) | tlv[5];
		if (efs->record_len < 1 || efs->record_len > 255) {
			fprintf(stderr,
			"error: SELECT response gives invalid record length\n");
			return(-1);
		}
		efs->record_count = tlv[6];
		efs->total_size = efs->record_len * efs->record_count;
		return(0);
	default:
		goto bad_file_desc;
	}
}