From 97958029f67eee3f87ad6da48afc3ac73cbbe27f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 28 Jan 2004 16:21:57 +0000 Subject: [PATCH] (iso7816_manage_security_env): New. (iso7816_decipher): Add PADIND argument. ** app-nks.c is now functional ** --- scd/ChangeLog | 5 ++ scd/app-nks.c | 199 ++++++++++++++++++++++++++++++++++++++++++++-- scd/app-openpgp.c | 3 +- scd/iso7816.c | 57 +++++++++---- scd/iso7816.h | 4 + 5 files changed, 246 insertions(+), 22 deletions(-) diff --git a/scd/ChangeLog b/scd/ChangeLog index 3a6a6aea4..66f48d467 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,8 @@ +2004-01-28 Werner Koch + + * iso7816.c (iso7816_manage_security_env): New. + (iso7816_decipher): Add PADIND argument. + 2004-01-27 Werner Koch * command.c (cmd_readcert, cmd_readkey): Work on a copy of LINE. diff --git a/scd/app-nks.c b/scd/app-nks.c index 0a04f7511..2ceb537a2 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -33,16 +33,18 @@ #include "tlv.h" static struct { - int fid; /* File ID. */ - int certtype; /* Type of certificate or 0 if it is not a certificate. */ + int fid; /* File ID. */ + int certtype; /* Type of certificate or 0 if it is not a certificate. */ int iskeypair; /* If true has the FID of the correspoding certificate. */ + int issignkey; /* True if file is a key usable for signing. */ + int isenckey; /* True if file is a key usable for decryption. */ } filelist[] = { - { 0x4531, 0, 0xC000 }, + { 0x4531, 0, 0xC000, 1, 0 }, { 0xC000, 101 }, { 0x4331, 100 }, { 0x4332, 100 }, { 0xB000, 110 }, - { 0x45B1, 0, 0xC200 }, + { 0x45B1, 0, 0xC200, 0, 1 }, { 0xC200, 101 }, { 0x43B1, 100 }, { 0x43B2, 100 }, @@ -356,6 +358,191 @@ do_readcert (app_t app, const char *certid, } +/* Verify the PIN if required. */ +static int +verify_pin (app_t app, + int (pincb)(void*, const char *, char **), + void *pincb_arg) +{ + /* Note that force_chv1 is never set but we do it here anyway so + that other applications may euse this function. For example it + makes sense to set force_chv1 for German signature law cards. + NKS is very similar to the DINSIG draft standard. */ + if (!app->did_chv1 || app->force_chv1 ) + { + char *pinvalue; + int rc; + + rc = pincb (pincb_arg, "PIN", &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + /* The follwoing limits are due to TCOS but also defined in the + NKS specs. */ + if (strlen (pinvalue) < 6) + { + log_error ("PIN is too short; minimum length is 6\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + else if (strlen (pinvalue) > 16) + { + log_error ("PIN is too large; maximum length is 16\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + /* Also it is possible to use a local PIN, we use the gloabl + PIN for this application. */ + rc = iso7816_verify (app->slot, 0, pinvalue, strlen (pinvalue)); + if (rc) + { + log_error ("verify PIN failed\n"); + xfree (pinvalue); + return rc; + } + app->did_chv1 = 1; + xfree (pinvalue); + } + + return 0; +} + + + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; + that callback should return the PIN in an allocated buffer and + store that in the 3rd argument. */ +static int +do_sign (app_t app, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, + 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; + int rc, i; + int fid; + unsigned char data[35]; /* Must be large enough for a SHA-1 digest + + the largest OID _prefix above. */ + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen != 20 && indatalen != 16 && indatalen != 35) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check that the provided ID is vaid. This is not really needed + but we do it to to enforce correct usage by the caller. */ + if (strncmp (keyidstr, "NKS-DF01.", 9) ) + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 9; + if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) + || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) + || keyidstr[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (keyidstr); + for (i=0; filelist[i].fid; i++) + if (filelist[i].iskeypair && filelist[i].fid == fid) + break; + if (!filelist[i].fid) + return gpg_error (GPG_ERR_NOT_FOUND); + if (!filelist[i].issignkey) + return gpg_error (GPG_ERR_INV_ID); + + /* Prepare the DER object from INDATA. */ + if (indatalen == 35) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. Check that it is waht we want and that + it matches the hash algorithm. */ + if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15)) + ; + else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15)) + ; + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data, indata, indatalen); + } + else + { + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, 15); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, 15); + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+15, indata, indatalen); + } + + rc = verify_pin (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); + return rc; +} + + + + +/* Decrypt the data in INDATA and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +static int +do_decipher (app_t app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + static const unsigned char mse_parm[] = { + 0x80, 1, 0x10, /* Select algorithm RSA. */ + 0x84, 1, 0x81 /* Select locak secret key 1 for descryption. */ + }; + int rc, i; + int fid; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check that the provided ID is vaid. This is not really needed + but we do it to to enforce correct usage by the caller. */ + if (strncmp (keyidstr, "NKS-DF01.", 9) ) + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 9; + if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) + || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) + || keyidstr[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (keyidstr); + for (i=0; filelist[i].fid; i++) + if (filelist[i].iskeypair && filelist[i].fid == fid) + break; + if (!filelist[i].fid) + return gpg_error (GPG_ERR_NOT_FOUND); + if (!filelist[i].isenckey) + return gpg_error (GPG_ERR_INV_ID); + + /* Do the TCOS specific MSE. */ + rc = iso7816_manage_security_env (app->slot, + 0xC1, 0xB8, + mse_parm, sizeof mse_parm); + if (!rc) + rc = verify_pin (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_decipher (app->slot, indata, indatalen, 0x81, + outdata, outdatalen); + return rc; +} + + /* Select the NKS 2.0 application on the card in SLOT. */ int @@ -375,9 +562,9 @@ app_select_nks (APP app) app->fnc.getattr = NULL; app->fnc.setattr = NULL; app->fnc.genkey = NULL; - app->fnc.sign = NULL; + app->fnc.sign = do_sign; app->fnc.auth = NULL; - app->fnc.decipher = NULL; + app->fnc.decipher = do_decipher; app->fnc.change_pin = NULL; app->fnc.check_pin = NULL; } diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 75e3e299e..021d6a52c 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1121,7 +1121,8 @@ do_decipher (APP app, const char *keyidstr, rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) - rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen); + rc = iso7816_decipher (app->slot, indata, indatalen, 0, + outdata, outdatalen); return rc; } diff --git a/scd/iso7816.c b/scd/iso7816.c index 9a15ce953..1ee9b55e7 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -1,5 +1,5 @@ /* iso7816.c - ISO 7816 commands - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -47,6 +47,7 @@ #define CMD_RESET_RETRY_COUNTER 0x2C #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA +#define CMD_MSE 0x22 #define CMD_PSO 0x2A #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 @@ -270,6 +271,23 @@ iso7816_put_data (int slot, int tag, return map_sw (sw); } +/* Manage Security Environment. This is a weird operation and there + is no easy abstraction for it. Furthermore, some card seem to have + a different interpreation of 7816-8 and thus we resort to let the + caller decide what to do. */ +gpg_error_t +iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, size_t datalen) +{ + int sw; + + if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 || !data || !datalen) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, datalen, data); + return map_sw (sw); +} + /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On success 0 is returned and the data is availavle in a newly @@ -301,13 +319,14 @@ iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, } -/* Perform the security operation DECIPHER. On - success 0 is returned and the plaintext is available in a newly - allocated buffer stored at RESULT with its length stored at - RESULTLEN. */ +/* Perform the security operation DECIPHER. PADIND is the padding + indicator to be used. It should be 0 if no padding is required, a + value of -1 suppresses the padding byte. On success 0 is returned + and the plaintext is available in a newly allocated buffer stored + at RESULT with its length stored at RESULTLEN. */ gpg_error_t iso7816_decipher (int slot, const unsigned char *data, size_t datalen, - unsigned char **result, size_t *resultlen) + int padind, unsigned char **result, size_t *resultlen) { int sw; unsigned char *buf; @@ -317,15 +336,23 @@ iso7816_decipher (int slot, const unsigned char *data, size_t datalen, *result = NULL; *resultlen = 0; - /* We need to prepend the padding indicator. */ - buf = xtrymalloc (datalen + 1); - if (!buf) - return out_of_core (); - *buf = 0; /* Padding indicator. */ - memcpy (buf+1, data, datalen); - sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, - result, resultlen); - xfree (buf); + if (padind >= 0) + { + /* We need to prepend the padding indicator. */ + buf = xtrymalloc (datalen + 1); + if (!buf) + return out_of_core (); + *buf = padind; /* Padding indicator. */ + memcpy (buf+1, data, datalen); + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, + result, resultlen); + xfree (buf); + } + else + { + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen, data, + result, resultlen); + } if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ diff --git a/scd/iso7816.h b/scd/iso7816.h index 98e688693..937326b6d 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -42,11 +42,15 @@ gpg_error_t iso7816_get_data (int slot, int tag, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_put_data (int slot, int tag, const unsigned char *data, size_t datalen); +gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, + const unsigned char *data, + size_t datalen); gpg_error_t iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_decipher (int slot, const unsigned char *data, size_t datalen, + int padind, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_internal_authenticate (int slot, const unsigned char *data, size_t datalen,