changeset 15:b70d35f5476f

fc-uicc-tool ported over
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 14 Mar 2021 07:41:09 +0000
parents b7ee2e85686b
children 53f8a1146a56
files .hgignore uicc/Makefile uicc/bfsearch.c uicc/cmdtab.c uicc/createfile.c uicc/dumpdir.c uicc/getresp.c uicc/hlread.c uicc/main.c uicc/pins.c uicc/readcmd.c uicc/readops.c uicc/select.c uicc/writecmd.c uicc/writeops.c
diffstat 15 files changed, 1389 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Mar 14 07:34:35 2021 +0000
+++ b/.hgignore	Sun Mar 14 07:41:09 2021 +0000
@@ -10,3 +10,5 @@
 ^pcsc/fc-pcsc-list$
 
 ^simtool/fc-simtool$
+
+^uicc/fc-uicc-tool$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/Makefile	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,20 @@
+CC=	gcc
+CFLAGS=	-O2
+CPPFLAGS=-I../libcommon
+PROG=	fc-uicc-tool
+OBJS=	bfsearch.o cmdtab.o createfile.o dumpdir.o getresp.o hlread.o main.o \
+	pins.o readcmd.o readops.o select.o writecmd.o writeops.o
+LIBS=	../libcommon/libcommon.a ../libutil/libutil.a
+INSTBIN=/opt/freecalypso/bin
+
+all:	${PROG}
+
+${PROG}:	${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS}
+
+install:
+	mkdir -p ${INSTBIN}
+	install -c ${PROG} ${INSTBIN}
+
+clean:
+	rm -f ${PROG} *.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/bfsearch.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,136 @@
+/*
+ * This module implements a brute force search of file ID space,
+ * both MF and ADF trees.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "file_id.h"
+
+extern u_char *extract_select_resp_tag();
+
+static
+bfsearch_dir(path, pathlen, siblings, nsiblings, outf)
+	unsigned *path, pathlen, *siblings, nsiblings;
+	FILE *outf;
+{
+	unsigned bfs, n;
+	unsigned df_children[255], ndfc;
+	unsigned childpath[8];
+	u_char *tlv_file_desc, *tlv_file_size;
+	int rc;
+
+	for (n = 0; n < pathlen; n++) {
+		rc = elem_select_op(path[n]);
+		if (rc < 0)
+			return(rc);
+		if ((sim_resp_sw & 0xFF00) != 0x6100) {
+			fprintf(stderr,
+				"error selecting 0x%04X: SW resp 0x%04X\n",
+				path[n], sim_resp_sw);
+			return(-1);
+		}
+	}
+	ndfc = 0;
+	for (bfs = 0; bfs <= 0xFFFF; bfs++) {
+		if (bfs == FILEID_MF || bfs == FILEID_ADF)
+			continue;
+		for (n = 0; n < pathlen; n++) {
+			if (bfs == path[n])
+				break;
+		}
+		if (n < pathlen)
+			continue;
+		for (n = 0; n < nsiblings; n++) {
+			if (bfs == siblings[n])
+				break;
+		}
+		if (n < nsiblings)
+			continue;
+		rc = elem_select_op(bfs);
+		if (rc < 0)
+			return(rc);
+		if (sim_resp_sw == 0x6A82)
+			continue;
+		if ((sim_resp_sw & 0xFF00) != 0x6100) {
+			for (n = 0; n < pathlen; n++)
+				fprintf(outf, "%04X/", path[n]);
+			fprintf(outf, "%04X: SW response 0x%04X\n", bfs,
+				sim_resp_sw);
+			continue;
+		}
+		rc = get_response_op();
+		if (rc < 0)
+			return(rc);
+		tlv_file_desc = extract_select_resp_tag(0x82);
+		if (!tlv_file_desc)
+			return(-1);
+		if (tlv_file_desc[1] < 1) {
+			fprintf(stderr,
+				"error: file desc TLV object is too short\n");
+			return(-1);
+		}
+		for (n = 0; n < pathlen; n++)
+			fprintf(outf, "%04X/", path[n]);
+		fprintf(outf, "%04X: file desc 0x%02X", bfs, tlv_file_desc[2]);
+		if ((tlv_file_desc[2] & 0xBF) == 0x38) {
+			fprintf(outf, ", DF\n");
+			if (ndfc < 255)
+				df_children[ndfc++] = bfs;
+		} else {
+			tlv_file_size = extract_select_resp_tag(0x80);
+			if (tlv_file_size && tlv_file_size[1] == 2)
+				fprintf(outf, ", total size %u",
+					(tlv_file_size[2] << 8) |
+					tlv_file_size[3]);
+			if (tlv_file_desc[1] == 5)
+				fprintf(outf, ", %u records of %u bytes",
+					tlv_file_desc[6],
+					(tlv_file_desc[4] << 8) |
+					tlv_file_desc[5]);
+			putc('\n', outf);
+		}
+		rc = elem_select_op(path[pathlen-1]);
+		if (rc < 0)
+			return(rc);
+		if ((sim_resp_sw & 0xFF00) != 0x6100) {
+			fprintf(stderr,
+			"reselecting starting file ID 0x%04X: SW resp 0x%04X\n",
+				path[pathlen-1], sim_resp_sw);
+			return(-1);
+		}
+	}
+	if (pathlen >= 8)
+		return(0);
+	for (n = 0; n < pathlen; n++)
+		childpath[n] = path[n];
+	for (n = 0; n < ndfc; n++) {
+		childpath[pathlen] = df_children[n];
+		rc = bfsearch_dir(childpath, pathlen + 1, df_children, ndfc,
+				  outf);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_bfsearch_mf(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned initpath;
+
+	initpath = FILEID_MF;
+	return bfsearch_dir(&initpath, 1, &initpath, 1, outf);
+}
+
+cmd_bfsearch_adf(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned initpath;
+
+	initpath = FILEID_ADF;
+	return bfsearch_dir(&initpath, 1, &initpath, 1, outf);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/cmdtab.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,74 @@
+/*
+ * This module contains the command table for fc-uicc-tool.
+ */
+
+#include "cmdtab.h"
+
+extern int cmd_apdu();
+extern int cmd_atr();
+extern int cmd_bfsearch_adf();
+extern int cmd_bfsearch_mf();
+extern int cmd_cd();
+extern int cmd_change_pin();
+extern int cmd_create_file();
+extern int cmd_delete_file();
+extern int cmd_dir();
+extern int cmd_disable_pin();
+extern int cmd_enable_pin();
+extern int cmd_exec();
+extern int cmd_exit();
+extern int cmd_get_response();
+extern int cmd_iccid();
+extern int cmd_pin_attempt_cnt();
+extern int cmd_puk_attempt_cnt();
+extern int cmd_readbin();
+extern int cmd_readef();
+extern int cmd_readrec();
+extern int cmd_select();
+extern int cmd_select_aid();
+extern int cmd_select_isim();
+extern int cmd_select_usim();
+extern int cmd_sim_resp();
+extern int cmd_unblock_pin();
+extern int cmd_update_bin();
+extern int cmd_update_bin_imm();
+extern int cmd_update_rec();
+extern int cmd_verify_hex();
+extern int cmd_verify_pin();
+
+struct cmdtab cmdtab[] = {
+	{"apdu", 1, 1, 0, cmd_apdu},
+	{"atr", 0, 0, 0, cmd_atr},
+	{"bfsearch-adf", 0, 0, 1, cmd_bfsearch_adf},
+	{"bfsearch-mf", 0, 0, 1, cmd_bfsearch_mf},
+	{"cd", 1, 1, 0, cmd_cd},
+	{"change-pin", 3, 3, 0, cmd_change_pin},
+	{"create-file", 1, 1, 0, cmd_create_file},
+	{"delete-file", 1, 1, 0, cmd_delete_file},
+	{"dir", 0, 0, 1, cmd_dir},
+	{"disable-pin", 2, 2, 0, cmd_disable_pin},
+	{"enable-pin", 2, 2, 0, cmd_enable_pin},
+	{"exec", 1, 1, 0, cmd_exec},
+	{"exit", 0, 1, 0, cmd_exit},
+	{"get-response", 1, 1, 1, cmd_get_response},
+	{"iccid", 0, 0, 1, cmd_iccid},
+	{"pin-attempt-cnt", 1, 1, 0, cmd_pin_attempt_cnt},
+	{"puk-attempt-cnt", 1, 1, 0, cmd_puk_attempt_cnt},
+	{"quit", 0, 1, 0, cmd_exit},
+	{"readbin", 2, 2, 1, cmd_readbin},
+	{"readef", 1, 1, 1, cmd_readef},
+	{"readrec", 1, 2, 1, cmd_readrec},
+	{"select", 1, 1, 1, cmd_select},
+	{"select-aid", 1, 1, 1, cmd_select_aid},
+	{"select-isim", 0, 0, 1, cmd_select_isim},
+	{"select-usim", 0, 0, 1, cmd_select_usim},
+	{"sim-resp", 0, 0, 1, cmd_sim_resp},
+	{"unblock-pin", 3, 3, 0, cmd_unblock_pin},
+	{"update-bin", 2, 2, 0, cmd_update_bin},
+	{"update-bin-imm", 2, 2, 0, cmd_update_bin_imm},
+	{"update-rec", 2, 2, 0, cmd_update_rec},
+	{"verify-ext", 2, 2, 0, cmd_verify_pin},
+	{"verify-hex", 2, 2, 0, cmd_verify_hex},
+	{"verify-pin", 2, 2, 0, cmd_verify_pin},
+	{0, 0, 0, 0, 0}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/createfile.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,81 @@
+/*
+ * This module implements commands that exercise ETSI TS 102 222
+ * CREATE FILE and DELETE FILE operations.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "simresp.h"
+
+cmd_create_file(argc, argv)
+	char **argv;
+{
+	u_char apdu[260], inbuf[252], *dp;
+	unsigned len1, len2;
+	int rc;
+
+	rc = read_hex_data_file(argv[1], inbuf, 252);
+	if (rc < 0)
+		return(rc);
+	len1 = rc;
+	dp = apdu + 5;
+	*dp++ = 0x62;
+	if (len1 < 0x80) {
+		*dp++ = len1;
+		len2 = len1 + 2;
+	} else {
+		*dp++ = 0x81;
+		*dp++ = len1;
+		len2 = len1 + 3;
+	}
+	bcopy(inbuf, dp, len1);
+	/* command header */
+	apdu[0] = 0x00;
+	apdu[1] = 0xE0;
+	apdu[2] = 0;
+	apdu[3] = 0;
+	apdu[4] = len2;
+	rc = apdu_exchange(apdu, len2 + 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_delete_file(argc, argv)
+	char **argv;
+{
+	u_char apdu[7];
+	unsigned file_id;
+	int rc;
+
+	if (!isxdigit(argv[1][0]) || !isxdigit(argv[1][1]) ||
+	    !isxdigit(argv[1][2]) || !isxdigit(argv[1][3]) || argv[1][4]) {
+		fprintf(stderr, "error: 4-digit hex argument required\n");
+		return(-1);
+	}
+	file_id = strtoul(argv[1], 0, 16);
+	/* form command APDU */
+	apdu[0] = 0x00;
+	apdu[1] = 0xE4;
+	apdu[2] = 0;
+	apdu[3] = 0;
+	apdu[4] = 2;
+	apdu[5] = file_id >> 8;
+	apdu[6] = file_id;
+	rc = apdu_exchange(apdu, 7);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/dumpdir.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,40 @@
+/*
+ * This module implements the dump of EF_DIR.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "file_id.h"
+
+cmd_dir(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned record_len, record_count;
+	unsigned recno;
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_DIR);
+	if (rc < 0)
+		return(rc);
+	rc = select_resp_get_linear_fixed(&record_len, &record_count);
+	if (rc < 0)
+		return(rc);
+	if (record_len < 5) {
+		fprintf(stderr, "error: EF_DIR record length is too short\n");
+		return(-1);
+	}
+	for (recno = 1; recno <= record_count; recno++) {
+		rc = readrec_op(recno, 0x04, record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		fprintf(outf, "Record #%u:\n", recno);
+		dump_efdir_record(outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/getresp.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,69 @@
+/*
+ * This module implements an elementary GET RESPONSE command
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_get_response(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	u_char cmd[5];
+	int rc;
+	unsigned len;
+
+	len = strtoul(argv[1], 0, 0);
+	if (len < 1 || len > 256) {
+		fprintf(stderr, "error: length argument is out of range\n");
+		return(-1);
+	}
+	/* GET RESPONSE command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xC0;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW resp to GET RESPONSE: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	display_sim_resp_in_hex(outf);
+	return(0);
+}
+
+get_response_op()
+{
+	u_char cmd[5];
+	int rc;
+	unsigned expect_resp_len;
+
+	expect_resp_len = sim_resp_sw & 0xFF;
+	/* GET RESPONSE command APDU */
+	cmd[0] = 0x00;
+	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: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != expect_resp_len) {
+		fprintf(stderr,
+			"error: GET RESPONSE returned %u bytes, expected %u\n",
+			sim_resp_data_len, expect_resp_len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/hlread.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,40 @@
+/*
+ * This module implements some high-level or user-friendly read commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "file_id.h"
+
+cmd_iccid(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned len;
+	char buf[21], *cp;
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ICCID);
+	if (rc < 0)
+		return(rc);
+	rc = select_resp_get_transparent(&len);
+	if (rc < 0)
+		return(rc);
+	if (len != 10) {
+		fprintf(stderr, "error: expected transparent EF of 10 bytes\n");
+		return(-1);
+	}
+	rc = readbin_op(0, 10);
+	if (rc < 0)
+		return(rc);
+	decode_reversed_nibbles(sim_resp_data, 10, buf);
+	for (cp = buf + 20; (cp > buf + 1) && (cp[-1] == 'F'); cp--)
+		;
+	*cp = '\0';
+	fprintf(outf, "%s\n", buf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/main.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+extern char be_reader_name[];
+extern char be_atr_string[];
+
+main(argc, argv)
+	char **argv;
+{
+	extern int optind;
+	char command[512];
+	int rc;
+
+	parse_global_options(argc, argv);
+	launch_backend();
+	collect_backend_init_strings();
+	if (argc > optind) {
+		rc = dispatch_ready_argv(argc - optind, argv + optind);
+		if (rc)
+			exit(1);
+		else
+			good_exit();
+	}
+	if (be_reader_name[0])
+		printf("Card reader name: %s\n", be_reader_name);
+	if (be_atr_string[0])
+		printf("ATR: %s\n", be_atr_string);
+	for (;;) {
+		if (isatty(0)) {
+			fputs("uicc> ", stdout);
+			fflush(stdout);
+		}
+		if (!fgets(command, sizeof command, stdin))
+			good_exit();
+		simtool_dispatch_cmd(command, 0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/pins.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,205 @@
+/*
+ * This module implements the standard set of commands for working
+ * with UICC PINs; because all of these commands take a user-specified
+ * P2 key ID, they should work with ADM PINs as well.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+
+cmd_verify_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* VERIFY PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_verify_hex(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* VERIFY PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 8;
+	rc = decode_hex_data_from_string(argv[2], cmd + 5, 8, 8);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_change_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* CHANGE PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x24;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 16;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[3], cmd + 13);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_disable_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* DISABLE PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x26;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_enable_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* ENABLE PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x28;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_unblock_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* UNBLOCK PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x2C;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 16;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[3], cmd + 13);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+/* retrieving PIN attempt counters from the card */
+
+cmd_pin_attempt_cnt(argc, argv)
+	char **argv;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* VERIFY PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 0;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	printf("%04X\n", sim_resp_sw);
+	return(0);
+}
+
+cmd_puk_attempt_cnt(argc, argv)
+	char **argv;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* UNBLOCK PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x2C;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 0;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	printf("%04X\n", sim_resp_sw);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/readcmd.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,102 @@
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+extern unsigned last_sel_file_record_len;
+
+cmd_readbin(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned offset, len;
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	len = strtoul(argv[2], 0, 0);
+	if (len < 1 || len > 256) {
+		fprintf(stderr, "error: length argument is out of range\n");
+		return(-1);
+	}
+	rc = readbin_op(offset, len);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex(outf);
+	return(0);
+}
+
+cmd_readrec(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned recno, len;
+	int rc;
+
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > 255) {
+		fprintf(stderr,
+			"error: record number argument is out of range\n");
+		return(-1);
+	}
+	if (argv[2]) {
+		len = strtoul(argv[2], 0, 0);
+		if (len < 1 || len > 255) {
+			fprintf(stderr,
+				"error: length argument is out of range\n");
+			return(-1);
+		}
+	} else {
+		if (!last_sel_file_record_len) {
+			fprintf(stderr,
+			"error: no current file record length is available\n");
+			return(-1);
+		}
+		len = last_sel_file_record_len;
+	}
+	rc = readrec_op(recno, 0x04, len);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex(outf);
+	return(0);
+}
+
+cmd_readef(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int file_id, rc;
+	unsigned file_len, readlen;
+
+	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);
+	rc = select_resp_get_transparent(&file_len);
+	if (rc < 0)
+		return(rc);
+	fprintf(outf, "Transparent EF of %u byte(s)\n", file_len);
+	if (!file_len)
+		return(0);
+	readlen = file_len;
+	if (readlen > 256)
+		readlen = 256;
+	rc = readbin_op(0, readlen);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex(outf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/readops.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,62 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+readbin_op(offset, len)
+	unsigned offset, len;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* READ BINARY command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xB0;
+	cmd[2] = offset >> 8;
+	cmd[3] = offset;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to READ BINARY: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != len) {
+		fprintf(stderr,
+			"error: READ BINARY returned %u bytes, expected %u\n",
+			sim_resp_data_len, len);
+		return(-1);
+	}
+	return(0);
+}
+
+readrec_op(recno, mode, len)
+	unsigned recno, mode, len;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* READ RECORD command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xB2;
+	cmd[2] = recno;
+	cmd[3] = mode;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to READ RECORD: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != len) {
+		fprintf(stderr,
+			"error: READ RECORD returned %u bytes, expected %u\n",
+			sim_resp_data_len, len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/select.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,394 @@
+#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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/writecmd.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,70 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern unsigned last_sel_file_record_len;
+
+cmd_update_bin(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	u_char data[255];
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	rc = read_hex_data_file(argv[2], data, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	return update_bin_op(offset, data, len);
+}
+
+cmd_update_bin_imm(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	u_char data[255];
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	rc = decode_hex_data_from_string(argv[2], data, 1, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	return update_bin_op(offset, data, len);
+}
+
+cmd_update_rec(argc, argv)
+	char **argv;
+{
+	unsigned recno;
+	u_char data[255];
+	int rc;
+
+	if (!last_sel_file_record_len) {
+		fprintf(stderr, "error: no record-based file selected\n");
+		return(-1);
+	}
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > 255) {
+		fprintf(stderr,
+			"error: record number argument is out of range\n");
+		return(-1);
+	}
+	rc = read_hex_data_file(argv[2], data, 255);
+	if (rc < 0)
+		return(rc);
+	if (rc != last_sel_file_record_len) {
+		fprintf(stderr, "error: hex data length != EF record length\n");
+		return(-1);
+	}
+	return update_rec_op(recno, 0x04, data, last_sel_file_record_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/writeops.c	Sun Mar 14 07:41:09 2021 +0000
@@ -0,0 +1,56 @@
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+update_bin_op(offset, data, len)
+	unsigned offset, len;
+	u_char *data;
+{
+	u_char cmd[260];
+	int rc;
+
+	/* UPDATE BINARY command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xD6;
+	cmd[2] = offset >> 8;
+	cmd[3] = offset;
+	cmd[4] = len;
+	bcopy(data, cmd + 5, len);
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to UPDATE BINARY: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+update_rec_op(recno, mode, data, len)
+	unsigned recno, mode, len;
+	u_char *data;
+{
+	u_char cmd[260];
+	int rc;
+
+	/* UPDATE RECORD command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xDC;
+	cmd[2] = recno;
+	cmd[3] = mode;
+	cmd[4] = len;
+	bcopy(data, cmd + 5, len);
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to UPDATE RECORD: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}