From d8f63eb6fcc1441c12a44850da2fa22a6fe81634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?=
 <ng.hong.quan@gmail.com>
Date: Thu, 11 Apr 2013 11:47:51 +0700
Subject: [PATCH 12/18] OpenPGP: Support write certificate for Gnuk.

---
 src/libopensc/card-openpgp.c | 158 +++++++++++++++++++++++++++++++++----------
 1 file changed, 123 insertions(+), 35 deletions(-)

diff --git a/src/libopensc/card-openpgp.c b/src/libopensc/card-openpgp.c
index 8a1a270..d9db948 100644
--- a/src/libopensc/card-openpgp.c
+++ b/src/libopensc/card-openpgp.c
@@ -725,6 +725,8 @@ pgp_iterate_blobs(struct blob *blob, int level, void (*func)())
 static int
 pgp_read_blob(sc_card_t *card, struct blob *blob)
 {
+	struct pgp_priv_data *priv = DRVDATA (card);
+
 	if (blob->data != NULL)
 		return SC_SUCCESS;
 	if (blob->info == NULL)
@@ -735,6 +737,11 @@ pgp_read_blob(sc_card_t *card, struct blob *blob)
 		size_t	buf_len = (card->caps & SC_CARD_CAP_APDU_EXT)
 				  ? sizeof(buffer) : 256;
 
+		/* Buffer length for certificate */
+		if (blob->id == DO_CERT && priv->max_cert_size > 0) {
+			buf_len = MIN(priv->max_cert_size, sizeof(buffer));
+		}
+
 		/* Buffer length for Gnuk pubkey */
 		if (card->type == SC_CARD_TYPE_OPENPGP_GNUK &&
 		    (blob->id == 0xa400 || blob->id == 0xb600 || blob->id == 0xb800
@@ -1190,49 +1197,75 @@ pgp_get_data(sc_card_t *card, unsigned int tag, u8 *buf, size_t buf_len)
 	LOG_FUNC_RETURN(card->ctx, apdu.resplen);
 }
 
-/* ABI: PUT DATA */
-static int
-pgp_put_data(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
+
+/* Internal: Write certificate for Gnuk */
+static int gnuk_write_certificate(sc_card_t *card, const u8 *buf, size_t length)
 {
+	sc_context_t *ctx = card->ctx;
+	size_t i = 0;
 	sc_apdu_t apdu;
+	u8 *part;
+	size_t plen;
+	int r = SC_SUCCESS;
+
+	LOG_FUNC_CALLED(ctx);
+
+	/* If null data is passed, delete certificate */
+	if (buf == NULL || length == 0) {
+		sc_format_apdu(card, &apdu, SC_APDU_CASE_1, 0xD6, 0x85, 0);
+		r = sc_transmit_apdu(card, &apdu);
+		LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
+		/* Check response */
+		r = sc_check_sw(card, apdu.sw1, apdu.sw2);
+		LOG_FUNC_RETURN(card->ctx, length);
+	}
+
+	/* Ref: gnuk_put_binary_libusb.py and gnuk_token.py in Gnuk source tree */
+	/* Split data to segments of 256 bytes. Send each segment via command chaining,
+	 * with particular P1 byte for each segment */
+	while (i*256 < length) {
+		part = (u8 *)buf + i*256;
+		plen = MIN(length - i*256, 256);
+
+		sc_log(card->ctx, "Write part %d from offset 0x%X, len %d", i+1, part, plen);
+
+		if (i == 0) {
+			sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xD6, 0x85, 0);
+		}
+		else {
+			sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xD6, i, 0);
+		}
+		apdu.flags |= SC_APDU_FLAGS_CHAINING;
+		apdu.data = part;
+		apdu.datalen = apdu.lc = plen;
+
+		r = sc_transmit_apdu(card, &apdu);
+		LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
+		/* Check response */
+		r = sc_check_sw(card, apdu.sw1, apdu.sw2);
+		LOG_TEST_RET(card->ctx, r, "UPDATE BINARY returned error");
+
+		/* To next part */
+		i++;
+	}
+	LOG_FUNC_RETURN(card->ctx, length);
+}
+
+
+/* Internal: Use PUT DATA command to write */
+static int
+pgp_put_data_plain(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
+{
 	struct pgp_priv_data *priv = DRVDATA(card);
-	struct blob *affected_blob = NULL;
-	struct do_info *dinfo = NULL;
+	sc_context_t *ctx = card->ctx;
+	sc_apdu_t apdu;
 	u8 ins = 0xDA;
 	u8 p1 = tag >> 8;
 	u8 p2 = tag & 0xFF;
 	u8 apdu_case = SC_APDU_CASE_3;
 	int r;
 
-	LOG_FUNC_CALLED(card->ctx);
-
-	/* Check if the tag is writable */
-	affected_blob = pgp_find_blob(card, tag);
-
-	/* Non-readable DOs have no represented blob, we have to check from pgp_get_info_by_tag */
-	if (affected_blob == NULL)
-		dinfo = pgp_get_info_by_tag(card, tag);
-	else
-		dinfo = affected_blob->info;
-
-	if (dinfo == NULL) {
-		sc_log(card->ctx, "The DO %04X does not exist.", tag);
-		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
-	}
-	else if ((dinfo->access & WRITE_MASK) == WRITE_NEVER) {
-		sc_log(card->ctx, "DO %04X is not writable.", tag);
-		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ALLOWED);
-	}
-
-	/* Check data size.
-	 * We won't check other DOs than 7F21 (certificate), because their capacity
-	 * is hard-codded and may change in various version of the card. If we check here,
-	 * the driver may be sticked to a limit version number of card.
-	 * 7F21 size is soft-coded, so we can check it. */
-	if (tag == DO_CERT && buf_len > priv->max_cert_size) {
-		sc_log(card->ctx, "Data size %ld exceeds DO size limit %ld.", buf_len, priv->max_cert_size);
-		LOG_FUNC_RETURN(card->ctx, SC_ERROR_WRONG_LENGTH);
-	}
+	LOG_FUNC_CALLED(ctx);
 
 	/* Extended Header list (004D DO) needs a variant of PUT DATA command */
 	if (tag == 0x004D) {
@@ -1258,15 +1291,70 @@ pgp_put_data(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
 		apdu.lc = buf_len;
 	}
 	else {
+		/* This case is to empty DO */
 		sc_format_apdu(card, &apdu, SC_APDU_CASE_1, ins, p1, p2);
 	}
 
 	/* Send APDU to card */
 	r = sc_transmit_apdu(card, &apdu);
-	LOG_TEST_RET(card->ctx, r, "APDU transmit failed");
+	LOG_TEST_RET(ctx, r, "APDU transmit failed");
 	/* Check response */
 	r = sc_check_sw(card, apdu.sw1, apdu.sw2);
 
+	if (r < 0)
+		LOG_FUNC_RETURN(ctx, r);
+
+	LOG_FUNC_RETURN(ctx, buf_len);
+}
+
+/* ABI: PUT DATA */
+static int
+pgp_put_data(sc_card_t *card, unsigned int tag, const u8 *buf, size_t buf_len)
+{
+	struct pgp_priv_data *priv = DRVDATA(card);
+	struct blob *affected_blob = NULL;
+	struct do_info *dinfo = NULL;
+	int r;
+
+	LOG_FUNC_CALLED(card->ctx);
+
+	/* Check if the tag is writable */
+	if (priv->current->id != tag)
+		affected_blob = pgp_find_blob(card, tag);
+
+	/* Non-readable DOs have no represented blob, we have to check from pgp_get_info_by_tag */
+	if (affected_blob == NULL)
+		dinfo = pgp_get_info_by_tag(card, tag);
+	else
+		dinfo = affected_blob->info;
+
+	if (dinfo == NULL) {
+		sc_log(card->ctx, "The DO %04X does not exist.", tag);
+		LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS);
+	}
+	else if ((dinfo->access & WRITE_MASK) == WRITE_NEVER) {
+		sc_log(card->ctx, "DO %04X is not writable.", tag);
+		LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ALLOWED);
+	}
+
+	/* Check data size.
+	 * We won't check other DOs than 7F21 (certificate), because their capacity
+	 * is hard-codded and may change in various version of the card. If we check here,
+	 * the driver may be sticked to a limit version number of card.
+	 * 7F21 size is soft-coded, so we can check it. */
+	if (tag == DO_CERT && buf_len > priv->max_cert_size) {
+		sc_log(card->ctx, "Data size %ld exceeds DO size limit %ld.", buf_len, priv->max_cert_size);
+		LOG_FUNC_RETURN(card->ctx, SC_ERROR_WRONG_LENGTH);
+	}
+
+	if (tag == DO_CERT && card->type == SC_CARD_TYPE_OPENPGP_GNUK) {
+		/* Gnuk need a special way to write certificate. */
+		r = gnuk_write_certificate(card, buf, buf_len);
+	}
+	else {
+		r = pgp_put_data_plain(card, tag, buf, buf_len);
+	}
+
 	/* Instruct more in case of error */
 	if (r == SC_ERROR_SECURITY_STATUS_NOT_SATISFIED) {
 		sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Please verify PIN first.");
-- 
1.9.3

