From 8eb9224f32ddf1c9e1490c4d9688a177f8b6ae64 Mon Sep 17 00:00:00 2001 From: Andreas Schwier Date: Fri, 18 Jul 2014 16:20:59 +0200 Subject: [PATCH] scd: Support for SmartCard-HSM * scd/app-sc-hsm.c: New. * scd/app.c (select_application, get_supported_applications): Register new app. -- Add a read/only driver for scdaemon that provides access to keys and certificates on a SmartCard-HSM (www.smartcard-hsm.com). The driver supports RSA and ECC keys on SmartCard-HSM cards and USB-Sticks. The driver does not yet support the MicroSD edition. -- ChangeLog and FSF copyright year fix by wk. --- doc/scdaemon.texi | 14 + scd/Makefile.am | 2 +- scd/app-common.h | 3 + scd/app-sc-hsm.c | 2020 +++++++++++++++++++++++++++++++++++++++++++++ scd/app.c | 3 + 5 files changed, 2041 insertions(+), 1 deletion(-) create mode 100644 scd/app-sc-hsm.c diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi index 861c8986b..79a5fccd5 100644 --- a/doc/scdaemon.texi +++ b/doc/scdaemon.texi @@ -340,6 +340,7 @@ stripping off the two leading dashes. * DINSIG Card:: The DINSIG card application * PKCS#15 Card:: The PKCS#15 card application * Geldkarte Card:: The Geldkarte application +* SmartCard-HSM:: The SmartCard-HSM application * Undefined Card:: The Undefined stub application @end menu @@ -382,6 +383,19 @@ This is a simple application to display information of a German Geldkarte. The Geldkarte is a small amount debit card application which comes with almost all German banking cards. +@node SmartCard-HSM +@subsection The SmartCard-HSM card application ``sc-hsm'' + +This application adds read/only support for keys and certificates +stored on a @uref{http://www.smartcard-hsm.com, SmartCard-HSM}. + +To generate keys and store certifiates you may use +@uref{https://github.com/OpenSC/OpenSC/wiki/SmartCardHSM, OpenSC} or +the tools from @uref{http://www.openscdp.org, OpenSCDP}. + +The SmartCard-HSM cards requires a card reader that supports Extended +Length APDUs. + @node Undefined Card @subsection The Undefined card application ``undefined'' diff --git a/scd/Makefile.am b/scd/Makefile.am index 215933afc..09dd7d242 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -33,7 +33,7 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) -card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c +card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c scdaemon_SOURCES = \ scdaemon.c scdaemon.h \ diff --git a/scd/app-common.h b/scd/app-common.h index 66430b61d..50046a4e3 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -223,6 +223,9 @@ gpg_error_t app_select_p15 (app_t app); /*-- app-geldkarte.c --*/ gpg_error_t app_select_geldkarte (app_t app); +/*-- app-sc-hsm.c --*/ +gpg_error_t app_select_sc_hsm (app_t app); + #endif diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c new file mode 100644 index 000000000..6e8df0afd --- /dev/null +++ b/scd/app-sc-hsm.c @@ -0,0 +1,2020 @@ +/* app-sc-hsm.c - The SmartCard-HSM card application (www.smartcard-hsm.com). + * Copyright (C) 2005 Free Software Foundation, Inc. + * Copyright (C) 2014 Andreas Schwier + * + * Code in this driver is based on app-p15.c with modifications + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "scdaemon.h" + +#include "iso7816.h" +#include "app-common.h" +#include "tlv.h" +#include "apdu.h" + + +/* The AID of the SmartCard-HSM applet. */ +static char const sc_hsm_aid[] = { 0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, + 0xC3, 0x1F, 0x02, 0x01 }; + + +/* Special file identifier for SmartCard-HSM */ +typedef enum +{ + SC_HSM_PRKD_PREFIX = 0xC4, + SC_HSM_CD_PREFIX = 0xC8, + SC_HSM_DCOD_PREFIX = 0xC9, + SC_HSM_CA_PREFIX = 0xCA, + SC_HSM_KEY_PREFIX = 0xCC, + SC_HSM_EE_PREFIX = 0xCE +} fid_prefix_type_t; + + +/* The key types supported by the SmartCard-HSM */ +typedef enum + { + KEY_TYPE_RSA, + KEY_TYPE_ECC + } key_type_t; + + +/* A bit array with for the key usage flags from the + commonKeyAttributes. */ +struct keyusage_flags_s +{ + unsigned int encrypt: 1; + unsigned int decrypt: 1; + unsigned int sign: 1; + unsigned int sign_recover: 1; + unsigned int wrap: 1; + unsigned int unwrap: 1; + unsigned int verify: 1; + unsigned int verify_recover: 1; + unsigned int derive: 1; + unsigned int non_repudiation: 1; +}; +typedef struct keyusage_flags_s keyusage_flags_t; + + + +/* This is an object to store information about a Certificate + Directory File (CDF) in a format suitable for further processing by + us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire CDF. */ +struct cdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct cdf_object_s *next; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* To avoid reading a certificate more than once, we cache it in an + allocated memory IMAGE of IMAGELEN. */ + size_t imagelen; + unsigned char *image; + + /* EF containing certificate */ + unsigned short fid; +}; +typedef struct cdf_object_s *cdf_object_t; + + + +/* This is an object to store information about a Private Key + Directory File (PrKDF) in a format suitable for further processing + by us. To keep memory management, simple we use a linked list of + items; i.e. one such object represents one certificate and the list + the entire PrKDF. */ +struct prkdf_object_s +{ + /* Link to next item when used in a linked list. */ + struct prkdf_object_s *next; + + /* Key type */ + key_type_t keytype; + + /* Key size in bits or 0 if unknown */ + size_t keysize; + + /* Length and allocated buffer with the Id of this object. */ + size_t objidlen; + unsigned char *objid; + + /* The key's usage flags. */ + keyusage_flags_t usageflags; + + /* The keyReference */ + unsigned char key_reference; +}; +typedef struct prkdf_object_s *prkdf_object_t; + + + +/* Context local to this application. */ +struct app_local_s +{ + /* Information on all certificates. */ + cdf_object_t certificate_info; + /* Information on all trusted certificates. */ + cdf_object_t trusted_certificate_info; + /* Information on all private keys. */ + prkdf_object_t private_key_info; +}; + + + +/*** Local prototypes. ***/ +static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen); + + + +/* Release the CDF object A */ +static void +release_cdflist (cdf_object_t a) +{ + while (a) + { + cdf_object_t tmp = a->next; + xfree (a->image); + xfree (a->objid); + xfree (a); + a = tmp; + } +} + + + +/* Release the PrKDF object A. */ +static void +release_prkdflist (prkdf_object_t a) +{ + while (a) + { + prkdf_object_t tmp = a->next; + xfree (a->objid); + xfree (a); + a = tmp; + } +} + + + +/* Release all local resources. */ +static void +do_deinit (app_t app) +{ + if (app && app->app_local) + { + release_cdflist (app->app_local->certificate_info); + release_cdflist (app->app_local->trusted_certificate_info); + release_prkdflist (app->app_local->private_key_info); + xfree (app->app_local); + app->app_local = NULL; + } +} + + + +/* Get the list of EFs from the SmartCard-HSM. On success a dynamically + * buffer containing the EF list is returned. The caller is responsible for + * freeing the buffer. + */ +static gpg_error_t +list_ef (int slot, unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send_le (slot, 1, 0x80, 0x58, 0x00, 0x00, -1, NULL, 65536, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + } + return iso7816_map_sw(sw); +} + + + +/* Do a select and a read for the file with EFID. EFID_DESC is a + description of the EF to be used with error messages. On success + BUFFER and BUFLEN contain the entire content of the EF. The caller + must free BUFFER only on success. */ +static gpg_error_t +select_and_read_binary (int slot, unsigned short efid, const char *efid_desc, + unsigned char **buffer, size_t *buflen, int maxread) +{ + gpg_error_t err; + unsigned char cdata[4]; + int sw; + + cdata[0] = 0x54; /* Create ISO 7861-4 odd ins READ BINARY */ + cdata[1] = 0x02; + cdata[2] = 0x00; + cdata[3] = 0x00; + + sw = apdu_send_le(slot, 1, 0x00, 0xB1, efid >> 8, efid & 0xFF, + 4, cdata, maxread, buffer, buflen); + + if (sw == 0x6282) + sw = 0x9000; + + err = iso7816_map_sw(sw); + if (err) + { + log_error ("error reading %s (0x%04X): %s\n", + efid_desc, efid, gpg_strerror (err)); + return err; + } + return 0; +} + + + +/* Parse a cert Id string (or a key Id string) and return the binary + object Id string in a newly allocated buffer stored at R_OBJID and + R_OBJIDLEN. On Error NULL will be stored there and an error code + returned. On success caller needs to free the buffer at R_OBJID. */ +static gpg_error_t +parse_certid (const char *certid, unsigned char **r_objid, size_t *r_objidlen) +{ + char tmpbuf[10]; + const char *s; + size_t objidlen; + unsigned char *objid; + int i; + + *r_objid = NULL; + *r_objidlen = 0; + + strcpy (tmpbuf, "HSM."); + if (strncmp (certid, tmpbuf, strlen (tmpbuf)) ) + { + if (!strncmp (certid, "HSM.", 4)) + return gpg_error (GPG_ERR_NOT_FOUND); + return gpg_error (GPG_ERR_INV_ID); + } + certid += strlen (tmpbuf); + + for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) + ; + if (*s || !objidlen || (objidlen%2)) + return gpg_error (GPG_ERR_INV_ID); + objidlen /= 2; + objid = xtrymalloc (objidlen); + if (!objid) + return gpg_error_from_syserror (); + for (s=certid, i=0; i < objidlen; i++, s+=2) + objid[i] = xtoi_2 (s); + *r_objid = objid; + *r_objidlen = objidlen; + return 0; +} + + + +/* Find a certificate object by the certificate ID CERTID and store a + pointer to it at R_CDF. */ +static gpg_error_t +cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + cdf_object_t cdf; + + err = parse_certid (certid, &objid, &objidlen); + if (err) + return err; + + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + if (!cdf) + for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) + break; + xfree (objid); + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_cdf = cdf; + return 0; +} + + + +/* Find a private key object by the key Id string KEYIDSTR and store a + pointer to it at R_PRKDF. */ +static gpg_error_t +prkdf_object_from_keyidstr (app_t app, const char *keyidstr, + prkdf_object_t *r_prkdf) +{ + gpg_error_t err; + size_t objidlen; + unsigned char *objid; + prkdf_object_t prkdf; + + err = parse_certid (keyidstr, &objid, &objidlen); + if (err) + return err; + + for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) + if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) + break; + xfree (objid); + if (!prkdf) + return gpg_error (GPG_ERR_NOT_FOUND); + *r_prkdf = prkdf; + return 0; +} + + + +/* Parse the BIT STRING with the keyUsageFlags from the + CommonKeyAttributes. */ +static gpg_error_t +parse_keyusage_flags (const unsigned char *der, size_t derlen, + keyusage_flags_t *usageflags) +{ + unsigned int bits, mask; + int i, unused, full; + + memset (usageflags, 0, sizeof *usageflags); + if (!derlen) + return gpg_error (GPG_ERR_INV_OBJ); + + unused = *der++; derlen--; + if ((!derlen && unused) || unused/8 > derlen) + return gpg_error (GPG_ERR_ENCODING_PROBLEM); + full = derlen - (unused+7)/8; + unused %= 8; + mask = 0; + for (i=1; unused; i <<= 1, unused--) + mask |= i; + + /* First octet */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->encrypt = 1; + if ((bits & 0x40)) usageflags->decrypt = 1; + if ((bits & 0x20)) usageflags->sign = 1; + if ((bits & 0x10)) usageflags->sign_recover = 1; + if ((bits & 0x08)) usageflags->wrap = 1; + if ((bits & 0x04)) usageflags->unwrap = 1; + if ((bits & 0x02)) usageflags->verify = 1; + if ((bits & 0x01)) usageflags->verify_recover = 1; + + /* Second octet. */ + if (derlen) + { + bits = *der++; derlen--; + if (full) + full--; + else + { + bits &= ~mask; + mask = 0; + } + } + else + bits = 0; + if ((bits & 0x80)) usageflags->derive = 1; + if ((bits & 0x40)) usageflags->non_repudiation = 1; + + return 0; +} + + + +/* Read and parse a Private Key Directory File containing a single + * key description in PKCS#15 format + * For each private key a matching certificate description is created, + * if the certificate EF exists and contains a X.509 certificate*/ +/* +0000 30 2A 30 13 0C 11 4A 6F 65 20 44 6F 65 20 28 52 0*0...Joe Doe (R +0010 53 41 32 30 34 38 29 30 07 04 01 01 03 02 02 74 SA2048)0.......t +0020 A1 0A 30 08 30 02 04 00 02 02 08 00 ..0.0....... +SEQUENCE SIZE( 42 ) + SEQUENCE SIZE( 19 ) + UTF8-STRING SIZE( 17 ) -- label + 0000 4A 6F 65 20 44 6F 65 20 28 52 53 41 32 30 34 38 Joe Doe (RSA2048 + 0010 29 ) + SEQUENCE SIZE( 7 ) + OCTET-STRING SIZE( 1 ) -- id + 0000 01 . + BIT-STRING SIZE( 2 ) -- key usage + 0000 02 74 .t + A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 10 ) + SEQUENCE SIZE( 8 ) + SEQUENCE SIZE( 2 ) + OCTET-STRING SIZE( 0 ) -- empty path, req object in PKCS#15 + INTEGER SIZE( 2 ) -- modulus size in bits + 0000 08 00 .. +*/ +static gpg_error_t +read_ef_prkd (app_t app, unsigned short fid, prkdf_object_t *prkdresult, + cdf_object_t *cdresult) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + int i; + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + prkdf_object_t prkdf = NULL; + cdf_object_t cdf = NULL; + unsigned long ul; + const unsigned char *objid; + size_t objidlen; + keyusage_flags_t usageflags; + const char *s; + key_type_t keytype; + size_t keysize; + + if (!fid) + return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */ + + err = select_and_read_binary (app->slot, fid, "PrKDF", &buffer, &buflen, 255); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || (tag != TAG_SEQUENCE && tag != 0x00))) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err)); + goto leave; + } + + keytype = tag == 0x00 ? KEY_TYPE_ECC : KEY_TYPE_RSA; + + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Parse the commonObjectAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Search the optional AuthId. We need to skip the optional + Label (UTF8STRING) and the optional CommonObjectFlags + (BITSTRING). */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + if (tag == TAG_UTF8_STRING) + { + ppp += objlen; /* Skip the Label. */ + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + } + if (tag == TAG_BIT_STRING) + { + ppp += objlen; /* Skip the CommonObjectFlags. */ + nnn -= objlen; + + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto no_authid; + if (err) + goto parse_error; + } + if (tag == TAG_OCTET_STRING && objlen) + { + /* AuthId ignored */ + } + no_authid: + ; + } + + /* Parse the commonKeyAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + objid = ppp; + objidlen = objlen; + ppp += objlen; + nnn -= objlen; + + /* Get the KeyUsageFlags. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + err = parse_keyusage_flags (ppp, objlen, &usageflags); + if (err) + goto parse_error; + ppp += objlen; + nnn -= objlen; + + /* Find the keyReference */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) + { + /* Skip the native element. */ + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) + { + /* Skip the accessFlags. */ + ppp += objlen; + nnn -= objlen; + + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (gpg_err_code (err) == GPG_ERR_EOF) + goto leave_cki; + if (!err && objlen > nnn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + } + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) + { + /* Yep, this is the keyReference. */ + for (ul=0; objlen; objlen--) + { + ul <<= 8; + ul |= (*ppp++) & 0xff; + nnn--; + } + } + + leave_cki: + ; + } + + + /* Skip subClassAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class == CLASS_CONTEXT && tag == 0) + { + pp += objlen; + nn -= objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + } + /* Parse the keyAttributes. */ + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + errstr = "unsupported reference type"; + goto parse_error; + } + + pp += objlen; + nn -= objlen; + + /* Parse the key size object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + keysize = 0; + if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && objlen == 2) + { + keysize = *pp++ << 8; + keysize += *pp++; + } + + + /* Create a new PrKDF list item. */ + prkdf = xtrycalloc (1, sizeof *prkdf); + if (!prkdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + prkdf->keytype = keytype; + prkdf->keysize = keysize; + prkdf->objidlen = objidlen; + prkdf->objid = xtrymalloc (objidlen); + if (!prkdf->objid) + { + err = gpg_error_from_syserror (); + xfree (prkdf); + goto leave; + } + memcpy (prkdf->objid, objid, objidlen); + + prkdf->usageflags = usageflags; + prkdf->key_reference = fid & 0xFF; + + log_debug ("PrKDF %04hX: id=", fid); + for (i=0; i < prkdf->objidlen; i++) + log_printf ("%02X", prkdf->objid[i]); + log_printf (" keyref=0x%02X", prkdf->key_reference); + log_printf (" keysize=%u", (unsigned int)prkdf->keysize); + log_printf (" usage="); + s = ""; + if (prkdf->usageflags.encrypt) log_printf ("%sencrypt", s), s = ","; + if (prkdf->usageflags.decrypt) log_printf ("%sdecrypt", s), s = ","; + if (prkdf->usageflags.sign ) log_printf ("%ssign", s), s = ","; + if (prkdf->usageflags.sign_recover) + log_printf ("%ssign_recover", s), s = ","; + if (prkdf->usageflags.wrap ) log_printf ("%swrap", s), s = ","; + if (prkdf->usageflags.unwrap ) log_printf ("%sunwrap", s), s = ","; + if (prkdf->usageflags.verify ) log_printf ("%sverify", s), s = ","; + if (prkdf->usageflags.verify_recover) + log_printf ("%sverify_recover", s), s = ","; + if (prkdf->usageflags.derive ) log_printf ("%sderive", s), s = ","; + if (prkdf->usageflags.non_repudiation) + log_printf ("%snon_repudiation", s), s = ","; + log_printf ("\n"); + + xfree (buffer); + buffer = NULL; + buflen = 0; + err = select_and_read_binary (app->slot, + (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF), "CertEF", &buffer, &buflen, 1); + + if (!err && buffer[0] == 0x30) + { + /* Create a matching CDF list item. */ + cdf = xtrycalloc (1, sizeof *cdf); + if (!cdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + cdf->objidlen = prkdf->objidlen; + cdf->objid = xtrymalloc (cdf->objidlen); + if (!cdf->objid) + { + err = gpg_error_from_syserror (); + xfree (cdf); + goto leave; + } + memcpy (cdf->objid, prkdf->objid, objidlen); + + cdf->fid = (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF); + + log_debug ("CDF %04hX: id=", fid); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + log_printf (" fid=%04X\n", cdf->fid); + } + goto leave; /* Ready. */ + + parse_error: + log_error ("error parsing PrKDF record (%d): %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + err = 0; + + leave: + xfree (buffer); + if (err) + { + if (prkdf) + { + if (prkdf->objid) + xfree (prkdf->objid); + xfree (prkdf); + } + if (cdf) + { + if (cdf->objid) + xfree (cdf->objid); + xfree (cdf); + } + } else { + prkdf->next = *prkdresult; + *prkdresult = prkdf; + if (cdf) + { + cdf->next = *cdresult; + *cdresult = cdf; + } + } + return err; +} + + + +/* Read and parse the Certificate Description File identified by FID. + On success a the CDF list gets stored at RESULT and the + caller is then responsible of releasing the object.*/ +/* +0000 30 35 30 11 0C 0B 43 65 72 74 69 66 69 63 61 74 050...Certificat +0010 65 03 02 06 40 30 16 04 14 C2 01 7C 2F BA A4 4A e...@0.....|/..J +0020 4A BB B8 49 11 DB 4A CA AA 7E 6A 2D 1B A1 08 30 J..I..J..~j-...0 +0030 06 30 04 04 02 CA 00 .0..... + +SEQUENCE SIZE( 53 ) + SEQUENCE SIZE( 17 ) + UTF8-STRING SIZE( 11 ) -- label + 0000 43 65 72 74 69 66 69 63 61 74 65 Certificate + BIT-STRING SIZE( 2 ) -- common object attributes + 0000 06 40 .@ + SEQUENCE SIZE( 22 ) + OCTET-STRING SIZE( 20 ) -- id + 0000 C2 01 7C 2F BA A4 4A 4A BB B8 49 11 DB 4A CA AA ..|/..JJ..I..J.. + 0010 7E 6A 2D 1B ~j-. + A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 8 ) + SEQUENCE SIZE( 6 ) + SEQUENCE SIZE( 4 ) + OCTET-STRING SIZE( 2 ) -- path + 0000 CA 00 .. + */ +static gpg_error_t +read_ef_cd (app_t app, unsigned short fid, cdf_object_t *result) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + size_t buflen; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, constructed, ndef; + int i; + const unsigned char *pp; + size_t nn; + int where; + const char *errstr = NULL; + cdf_object_t cdf = NULL; + const unsigned char *objid; + size_t objidlen; + + if (!fid) + return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */ + + err = select_and_read_binary (app->slot, fid, "CDF", &buffer, &buflen, 255); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing CDF record: %s\n", gpg_strerror (err)); + goto leave; + } + pp = p; + nn = objlen; + p += objlen; + n -= objlen; + + /* Skip the commonObjectAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + pp += objlen; + nn -= objlen; + + /* Parse the commonCertificateAttributes. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + { + const unsigned char *ppp = pp; + size_t nnn = objlen; + + pp += objlen; + nn -= objlen; + + /* Get the Id. */ + where = __LINE__; + err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nnn + || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + objid = ppp; + objidlen = objlen; + } + + /* Parse the certAttribute. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > nn + || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + nn = objlen; + + /* Check that the reference is a Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto parse_error; + } + nn = objlen; + + /* Parse the Path object. */ + where = __LINE__; + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && objlen > nn) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + goto parse_error; + + /* Make sure that the next element is a non zero path and of + even length (FID are two bytes each). */ + if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING + || (objlen & 1) ) + { + errstr = "invalid path reference"; + goto parse_error; + } + /* Create a new CDF list item. */ + cdf = xtrycalloc (1, sizeof *cdf); + if (!cdf) + { + err = gpg_error_from_syserror (); + goto leave; + } + cdf->objidlen = objidlen; + cdf->objid = xtrymalloc (objidlen); + if (!cdf->objid) + { + err = gpg_error_from_syserror (); + xfree (cdf); + goto leave; + } + memcpy (cdf->objid, objid, objidlen); + + cdf->fid = (SC_HSM_CA_PREFIX << 8) | (fid & 0xFF); + + log_debug ("CDF %04hX: id=", fid); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + + goto leave; + + parse_error: + log_error ("error parsing CDF record (%d): %s - skipped\n", + where, errstr? errstr : gpg_strerror (err)); + err = 0; + + leave: + xfree (buffer); + if (err) + { + if (cdf) + { + if (cdf->objid) + xfree (cdf->objid); + xfree (cdf); + } + } + else + { + cdf->next = *result; + *result = cdf; + } + return err; +} + + + +/* Read the device certificate and extract the serial number + +EF.C_DevAut (2F02) contains two CVCs, the first is the device certificate, the +second is the issuer certificate +0000 7F 21 81 E2 7F 4E 81 9B 5F 29 01 00 42 0B 55 54 .!...N.._)..B.UT +0010 43 43 30 32 30 30 30 30 32 7F 49 4F 06 0A 04 00 CC0200002.IO.... +0020 7F 00 07 02 02 02 02 03 86 41 04 6D FF D6 85 57 .........A.m...W +0030 40 FB 10 5D 94 71 8A 94 D2 5E 50 33 E7 1E C0 6C @..].q...^P3...l +0040 63 D5 C8 FC BA F3 02 1D 70 23 F6 47 E8 35 48 EF c.......p#.G.5H. +0050 B5 94 72 3C 6F BE C0 EB 9A C7 FB 06 59 26 CF 65 ..r...<. +0150 6B AC 06 EA 5F 20 0B 55 54 43 43 30 32 30 30 30 k..._ .UTCC02000 +0160 30 32 7F 4C 10 06 0B 2B 06 01 04 01 81 C3 1F 03 02.L...+........ +0170 01 01 53 01 80 5F 25 06 01 03 00 03 02 08 5F 24 ..S.._%......._$ +0180 06 02 01 00 03 02 07 5F 37 40 93 C1 42 8B B3 8E ......._7@..B... +0190 42 61 6F 2C 19 E6 98 41 BD AA 60 BD E0 DD 4E F0 Bao,...A..`...N. +01A0 15 D5 4F 71 B7 BB C3 3A F2 AD 27 5E DD EE 6D 12 ..Oq...:..'^..m. +01B0 76 E6 2B A0 4C 01 CA C1 26 0C 45 6D C6 CB EC 92 v.+.L...&.Em.... +01C0 BF 38 18 AD 8F B2 29 40 A9 51 .8....)@.Q + +The certificate format is defined in BSI TR-03110 + +7F21 [ APPLICATION 33 ] IMPLICIT SEQUENCE SIZE( 226 ) + 7F4E [ APPLICATION 78 ] IMPLICIT SEQUENCE SIZE( 155 ) + 5F29 [ APPLICATION 41 ] SIZE( 1 ) -- profile id + 0000 00 . + 42 [ APPLICATION 2 ] SIZE( 11 ) -- CAR + 0000 55 54 43 43 30 32 30 30 30 30 32 UTCC0200002 + 7F49 [ APPLICATION 73 ] IMPLICIT SEQUENCE SIZE( 79 ) -- public key + OBJECT IDENTIFIER = { id-TA-ECDSA-SHA-256 } + 86 [ CONTEXT 6 ] SIZE( 65 ) + 0000 04 6D FF D6 85 57 40 FB 10 5D 94 71 8A 94 D2 5E .m...W@..].q...^ + 0010 50 33 E7 1E C0 6C 63 D5 C8 FC BA F3 02 1D 70 23 P3...lc.......p# + 0020 F6 47 E8 35 48 EF B5 94 72 3C 6F BE C0 EB 9A C7 .G.5H...rslot, 0x2F02, "EF.C_DevAut", + &buffer, &buflen, 512); + if (err) + return err; + + p = buffer; + n = buflen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (!err && (objlen > n || tag != 0x21)) + err = gpg_error (GPG_ERR_INV_OBJ); + if (err) + { + log_error ("error parsing C_DevAut: %s\n", gpg_strerror (err)); + goto leave; + } + + chr = find_tlv (p, objlen, 0x5F20, &chrlen); + if (!chr) + { + err = gpg_error (GPG_ERR_INV_OBJ); + log_error ("CHR not found in CVC\n"); + goto leave; + } + + chrlen -= 5; + + app->serialno = xtrymalloc (chrlen); + if (!app->serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + + app->serialnolen = chrlen; + memcpy(app->serialno, chr, chrlen); + + leave: + xfree (buffer); + return err; +} + + + +/* Get all the basic information from the SmartCard-HSM, check the + structure and initialize our local context. This is used once at + application initialization. */ +static gpg_error_t +read_meta (app_t app) +{ + gpg_error_t err; + unsigned char *eflist = NULL; + size_t eflistlen = 0; + int i; + + err = read_serialno(app); + if (err) + return err; + + err = list_ef (app->slot, &eflist, &eflistlen); + if (err) + return err; + + for (i = 0; i < eflistlen; i += 2) { + switch(eflist[i]) { + case SC_HSM_KEY_PREFIX: + if (eflist[i + 1] == 0) /* No key with ID=0 */ + break; + err = read_ef_prkd (app, (SC_HSM_PRKD_PREFIX << 8) | eflist[i + 1], + &app->app_local->private_key_info, + &app->app_local->certificate_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + break; + case SC_HSM_CD_PREFIX: + err = read_ef_cd (app, (eflist[i] << 8) | eflist[i + 1], + &app->app_local->trusted_certificate_info); + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + err = 0; + if (err) + return err; + break; + } + } + + xfree (eflist); + + return err; +} + + + +/* Helper to do_learn_status: Send information about all certificates + listed in CERTINFO back. Use CERTTYPE as type of the + certificate. */ +static gpg_error_t +send_certinfo (ctrl_t ctrl, const char *certtype, cdf_object_t certinfo) +{ + for (; certinfo; certinfo = certinfo->next) + { + char *buf, *p; + + buf = xtrymalloc (9 + certinfo->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (certinfo->objid, certinfo->objidlen, p); + + send_status_info (ctrl, "CERTINFO", + certtype, strlen (certtype), + buf, strlen (buf), + NULL, (size_t)0); + xfree (buf); + } + return 0; +} + + + +/* Get the keygrip of the private key object PRKDF. On success the + keygrip gets returned in the caller provided 41 byte buffer + R_GRIPSTR. */ +static gpg_error_t +keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr) +{ + gpg_error_t err; + cdf_object_t cdf; + unsigned char *der; + size_t derlen; + ksba_cert_t cert; + + /* Look for a matching certificate. A certificate matches if the Id + matches the one of the private key info. */ + for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) + if (cdf->objidlen == prkdf->objidlen + && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) + break; + if (!cdf) + return gpg_error (GPG_ERR_NOT_FOUND); + + err = readcert_by_cdf (app, cdf, &der, &derlen); + if (err) + return err; + + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); + if (!err) + err = app_help_get_keygrip_string (cert, r_gripstr); + ksba_cert_release (cert); + + return err; +} + + + +/* Helper to do_learn_status: Send information about all known + keypairs back. */ +static gpg_error_t +send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo) +{ + gpg_error_t err; + + for (; keyinfo; keyinfo = keyinfo->next) + { + char gripstr[40+1]; + char *buf, *p; + + buf = xtrymalloc (9 + keyinfo->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (keyinfo->objid, keyinfo->objidlen, p); + + err = keygripstr_from_prkdf (app, keyinfo, gripstr); + if (err) + { + log_error ("can't get keygrip from %04X\n", keyinfo->key_reference); + } + else + { + assert (strlen (gripstr) == 40); + send_status_info (ctrl, "KEYPAIRINFO", + gripstr, 40, + buf, strlen (buf), + NULL, (size_t)0); + } + xfree (buf); + } + return 0; +} + + + +/* This is the handler for the LEARN command. */ +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + if ((flags & 1)) + err = 0; + else + { + err = send_certinfo (ctrl, "100", app->app_local->certificate_info); + if (!err) + err = send_certinfo (ctrl, "101", + app->app_local->trusted_certificate_info); + } + + if (!err) + err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); + + return err; +} + + + +/* Read a certificate using the information in CDF and return the + certificate in a newly allocated buffer R_CERT and its length + R_CERTLEN. */ +static gpg_error_t +readcert_by_cdf (app_t app, cdf_object_t cdf, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + const unsigned char *p, *save_p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca; + int i; + + *r_cert = NULL; + *r_certlen = 0; + + /* First check whether it has been cached. */ + if (cdf->image) + { + *r_cert = xtrymalloc (cdf->imagelen); + if (!*r_cert) + return gpg_error_from_syserror (); + memcpy (*r_cert, cdf->image, cdf->imagelen); + *r_certlen = cdf->imagelen; + return 0; + } + + err = select_and_read_binary (app->slot, cdf->fid, "CD", &buffer, &buflen, 4096); + if (err) + { + log_error ("error reading certificate with Id "); + for (i=0; i < cdf->objidlen; i++) + log_printf ("%02X", cdf->objid[i]); + log_printf (": %s\n", gpg_strerror (err)); + goto leave; + } + + /* Check whether this is really a certificate. */ + p = buffer; + n = buflen; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) + rootca = 0; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (!rootca + && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + /* The certificate seems to be contained in a userCertificate + container. Skip this and assume the following sequence is + the certificate. */ + if (n < objlen) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + p += objlen; + n -= objlen; + save_p = p; + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + totobjlen = objlen + hdrlen; + assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + *r_cert = buffer; + buffer = NULL; + *r_certlen = totobjlen; + + /* Try to cache it. */ + if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen))) + { + memcpy (cdf->image, *r_cert, *r_certlen); + cdf->imagelen = *r_certlen; + } + + + leave: + xfree (buffer); + return err; +} + + + +/* Handler for the READCERT command. + + Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer to be stored at R_CERT and its length at R_CERTLEN. A error + code will be returned on failure and R_CERT and R_CERTLEN will be + set to NULL/0. */ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **r_cert, size_t *r_certlen) +{ + gpg_error_t err; + cdf_object_t cdf; + + *r_cert = NULL; + *r_certlen = 0; + err = cdf_object_from_certid (app, certid, &cdf); + if (!err) + err =readcert_by_cdf (app, cdf, r_cert, r_certlen); + return err; +} + + + +/* Implement the GETATTR command. This is similar to the LEARN + command but returns just one value via the status interface. */ +static gpg_error_t +do_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + if (!strcmp (name, "$AUTHKEYID")) + { + char *buf, *p; + prkdf_object_t prkdf; + + /* We return the ID of the first private key capable of + signing. */ + for (prkdf = app->app_local->private_key_info; prkdf; + prkdf = prkdf->next) + if (prkdf->usageflags.sign) + break; + if (prkdf) + { + buf = xtrymalloc (9 + prkdf->objidlen*2 + 1); + if (!buf) + return gpg_error_from_syserror (); + p = stpcpy (buf, "HSM."); + bin2hex (prkdf->objid, prkdf->objidlen, p); + + send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); + xfree (buf); + return 0; + } + } + else if (!strcmp (name, "$DISPSERIALNO")) + { + send_status_info (ctrl, name, app->serialno, app->serialnolen, NULL, 0); + return 0; + } + + return gpg_error (GPG_ERR_INV_NAME); +} + + + +/* Apply PKCS#1 V1.5 padding for signature operation + * The function combines padding, digest info and the hash value. The buffer + * must be allocated by the caller matching the key size + */ +static +void apply_PKCS_padding(const unsigned char *dig, int diglen, + const unsigned char *prefix, int prefixlen, + unsigned char *buff, int bufflen) +{ + int i; + + // Caller must ensure sufficient buffer + if (diglen + prefixlen + 4 > bufflen) + return; + + *buff++ = 0x00; + *buff++ = 0x01; + for (i = bufflen - diglen - prefixlen - 3; i > 0; i--) + *buff++ = 0xFF; + + *buff++ = 0x00; + if (prefix) + memcpy(buff, prefix, prefixlen); + buff+= prefixlen; + memcpy(buff, dig, diglen); +} + + + +/* + * Decode a digest info structure to extract the hash value. The + * buffer to receive the hash must be provided by the caller with + * hashlen pointing to the inbound length. hashlen is updated to the + * outbound length + */ +static +int hash_from_digestinfo(const unsigned char *di, size_t dilen, + unsigned char *hash, size_t *hashlen) +{ + const unsigned char *p,*pp; + size_t n, nn, objlen, hdrlen; + int class, tag, constructed, ndef; + gpg_error_t err; + + p = di; + n = dilen; + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + + if (!err && (objlen > n || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + pp = p; + nn = objlen; + + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + + if (!err && (objlen > nn || tag != TAG_SEQUENCE)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + pp += objlen; + nn -= objlen; + + err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + + if (!err && (objlen > nn || tag != TAG_OCTET_STRING)) + err = gpg_error (GPG_ERR_INV_OBJ); + if ( err ) + return err; + + if (*hashlen < objlen) + return gpg_error (GPG_ERR_TOO_SHORT); + + memcpy(hash, pp, objlen); + *hashlen = objlen; + + return err; +} + + + +/* Perform PIN verification + */ +static gpg_error_t +verify_pin(app_t app, gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + pininfo_t pininfo; + char *pinvalue; + char *prompt; + int sw; + + sw = apdu_send_simple (app->slot, 0, 0x00, ISO7816_VERIFY, 0x00, 0x81, + -1, NULL); + + if (sw == SW_SUCCESS) + return 0; /* PIN already verified */ + + if (sw == 0x6984) { + log_error ("SmartCard-HSM not initialized. Run sc-hsm-tool first\n"); + return gpg_error (GPG_ERR_NO_PIN); + } + + if (sw == SW_CHV_BLOCKED) { + log_error ("PIN Blocked\n"); + return gpg_error (GPG_ERR_PIN_BLOCKED); + } + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = 0; + pininfo.minlen = 6; + pininfo.maxlen = 15; + + prompt = "||Please enter the PIN"; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + err = pincb (pincb_arg, prompt, NULL); + if (err) + { + log_info ("PIN callback returned error: %s\n", + gpg_strerror (err)); + return err; + } + + err = iso7816_verify_kp (app->slot, 0x81, &pininfo); + pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ + } + else + { + err = pincb (pincb_arg, prompt, &pinvalue); + if (err) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); + return err; + } + + err = iso7816_verify (app->slot, 0x81, pinvalue, strlen(pinvalue)); + xfree (pinvalue); + } + if (err) + { + log_error ("PIN verification failed: %s\n", gpg_strerror (err)); + return err; + } + log_debug ("PIN verification succeeded\n"); + return err; +} + + + +/* Handler for the PKSIGN command. + + 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 as the 3rd argument. + + The API is somewhat inconsistent: The caller can either supply + a plain hash and the algorithm in hashalgo or a complete + DigestInfo structure. The former is detect by characteristic length + of the provided data (20,28,32,48 or 64 byte). + + The function returns the RSA block in the size of the modulus or + the ECDSA signature in X9.62 format (SEQ/INT(r)/INT(s)) +*/ +static gpg_error_t +do_sign (app_t app, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + 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 }; + static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ + { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1C }; + static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; + static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ + { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 }; + static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 }; + + gpg_error_t err; + unsigned char cdsblk[256]; /* Raw PKCS#1 V1.5 block with padding (RSA) or hash */ + prkdf_object_t prkdf; /* The private key object. */ + size_t cdsblklen; + unsigned char algoid; + int sw; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + if (indatalen > 124) /* Limit for 1024 bit key */ + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover + ||prkdf->usageflags.non_repudiation)) + { + log_error ("key %s may not be used for signing\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (prkdf->keytype == KEY_TYPE_RSA) + { + algoid = 0x20; + + cdsblklen = prkdf->keysize >> 3; + if (!cdsblklen) + cdsblklen = 256; + + if (hashalgo == GCRY_MD_SHA1 && indatalen == 20) + apply_PKCS_padding(indata, indatalen, sha1_prefix, sizeof(sha1_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_MD5 && indatalen == 20) + apply_PKCS_padding(indata, indatalen, rmd160_prefix, sizeof(rmd160_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA224 && indatalen == 28) + apply_PKCS_padding(indata, indatalen, sha224_prefix, sizeof(sha224_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA256 && indatalen == 32) + apply_PKCS_padding(indata, indatalen, sha256_prefix, sizeof(sha256_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA384 && indatalen == 48) + apply_PKCS_padding(indata, indatalen, sha384_prefix, sizeof(sha384_prefix), + cdsblk, cdsblklen); + else if (hashalgo == GCRY_MD_SHA512 && indatalen == 64) + apply_PKCS_padding(indata, indatalen, sha512_prefix, sizeof(sha512_prefix), + cdsblk, cdsblklen); + else /* Assume it's already a digest info or TLS_MD5SHA1 */ + apply_PKCS_padding(indata, indatalen, NULL, 0, cdsblk, cdsblklen); + } + else + { + algoid = 0x70; + if (indatalen != 20 && indatalen != 28 && indatalen != 32 && + indatalen != 48 && indatalen != 64) + { + cdsblklen = sizeof(cdsblk); + err = hash_from_digestinfo(indata, indatalen, cdsblk, &cdsblklen); + if (err) + { + log_error ("DigestInfo invalid : %s\n", gpg_strerror (err)); + return err; + } + + } + else + { + memcpy(cdsblk, indata, indatalen); + cdsblklen = indatalen; + } + } + + err = verify_pin(app, pincb, pincb_arg); + if (err) + return err; + + sw = apdu_send_le (app->slot, 1, 0x80, 0x68, prkdf->key_reference, algoid, + cdsblklen, cdsblk, 0, outdata, outdatalen); + return iso7816_map_sw(sw); +} + + + +/* Handler for the PKAUTH command. + + This is basically the same as the PKSIGN command but we first check + that the requested key is suitable for authentication; that is, it + must match the criteria used for the attribute $AUTHKEYID. See + do_sign for calling conventions; there is no HASHALGO, though. */ +static gpg_error_t +do_auth (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + prkdf_object_t prkdf; + int algo; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!prkdf->usageflags.sign) + { + log_error ("key %s may not be used for authentication\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1; + return do_sign (app, keyidstr, algo, pincb, pincb_arg, + indata, indatalen, outdata, outdatalen); +} + + + +/* Check PKCS#1 V1.5 padding and extract plain text. + * The function allocates a buffer for the plain text. The caller must release + * the buffer + */ +static gpg_error_t +strip_PKCS15_padding(unsigned char *src, int srclen, unsigned char **dst, + size_t *dstlen) +{ + int c1,c2,c3; + unsigned char *p; + + c1 = *src++ == 0x00; + c2 = *src++ == 0x02; + srclen -= 2; + while ((srclen > 0) && *src) + { + src++; + srclen--; + } + c3 = srclen > 0; + + if (!(c1 && c2 && c3)) + return gpg_error (GPG_ERR_DECRYPT_FAILED); + + src++; + srclen--; + + p = xtrymalloc (srclen); + if (!p) + return gpg_error_from_syserror (); + + memcpy(p, src, srclen); + *dst = p; + *dstlen = srclen; + + return 0; +} + + + +/* Decrypt a PKCS#1 V1.5 formatted cryptogram using the referenced key + */ +static gpg_error_t +do_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + gpg_error_t err; + unsigned char p1blk[256]; /* Enciphered P1 block */ + prkdf_object_t prkdf; /* The private key object. */ + unsigned char *rspdata; + size_t rspdatalen; + size_t p1blklen; + int ofs, sw; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); + if (err) + return err; + if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) + { + log_error ("key %s may not be used for deciphering\n", keyidstr); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if (prkdf->keytype != KEY_TYPE_RSA) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + p1blklen = prkdf->keysize >> 3; + if (!p1blklen) + p1blklen = 256; + + /* Due to MPI the input may be shorter or longer than the block size */ + memset(p1blk, 0, sizeof(p1blk)); + ofs = p1blklen - indatalen; + if (ofs < 0) + memcpy(p1blk, (unsigned char *)indata - ofs, p1blklen); + else + memcpy(p1blk + ofs, indata, indatalen); + + err = verify_pin(app, pincb, pincb_arg); + if (err) + return err; + + sw = apdu_send_le (app->slot, 1, 0x80, 0x62, prkdf->key_reference, 0x21, + p1blklen, p1blk, 0, &rspdata, &rspdatalen); + err = iso7816_map_sw(sw); + if (err) + { + log_error ("Decrypt failed: %s\n", gpg_strerror (err)); + return err; + } + + err = strip_PKCS15_padding(rspdata, rspdatalen, outdata, outdatalen); + xfree(rspdata); + + if (!err) + *r_info |= APP_DECIPHER_INFO_NOPAD; + + return err; +} + + + +/* + * Select the SmartCard-HSM application on the card in SLOT. + */ +gpg_error_t +app_select_sc_hsm (app_t app) +{ + int slot = app->slot; + int rc; + + rc = iso7816_select_application (slot, sc_hsm_aid, sizeof sc_hsm_aid, 0); + if (!rc) + { + app->apptype = "SC-HSM"; + + app->app_local = xtrycalloc (1, sizeof *app->app_local); + if (!app->app_local) + { + rc = gpg_error_from_syserror (); + goto leave; + } + + rc = read_meta (app); + if (rc) + goto leave; + + app->fnc.deinit = do_deinit; + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.getattr = do_getattr; + app->fnc.setattr = NULL; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = NULL; + app->fnc.check_pin = NULL; + + leave: + if (rc) + do_deinit (app); + } + + return rc; +} diff --git a/scd/app.c b/scd/app.c index a0bb5f5ac..1694ea1c4 100644 --- a/scd/app.c +++ b/scd/app.c @@ -387,6 +387,8 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app) err = app_select_geldkarte (app); if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) err = app_select_dinsig (app); + if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm"))) + err = app_select_sc_hsm (app); if (err && name) err = gpg_error (GPG_ERR_NOT_SUPPORTED); @@ -422,6 +424,7 @@ get_supported_applications (void) "p15", "geldkarte", "dinsig", + "sc-hsm", /* Note: "undefined" is not listed here because it needs special treatment by the client. */ NULL