diff options
Diffstat (limited to 'scd/app-dinsig.c')
-rw-r--r-- | scd/app-dinsig.c | 574 |
1 files changed, 574 insertions, 0 deletions
diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c new file mode 100644 index 0000000..5a2713e --- /dev/null +++ b/scd/app-dinsig.c @@ -0,0 +1,574 @@ +/* app-dinsig.c - The DINSIG (DIN V 66291-1) card application. + * Copyright (C) 2002, 2004, 2005, 2007, 2008 Free Software Foundation, Inc. + * + * 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 <https://www.gnu.org/licenses/>. + */ + + +/* The German signature law and its bylaw (SigG and SigV) is currently + used with an interface specification described in DIN V 66291-1. + The AID to be used is: 'D27600006601'. + + The file IDs for certificates utilize the generic format: + Cxyz + C being the hex digit 'C' (12). + x being the service indicator: + '0' := SigG conform digital signature. + '1' := entity authentication. + '2' := key encipherment. + '3' := data encipherment. + '4' := key agreement. + other values are reserved for future use. + y being the security environment number using '0' for cards + not supporting a SE number. + z being the certificate type: + '0' := C.CH (base certificate of card holder) or C.ICC. + '1' .. '7' := C.CH (business or professional certificate + of card holder. + '8' .. 'D' := C.CA (certificate of a CA issue by the Root-CA). + 'E' := C.RCA (self certified certificate of the Root-CA). + 'F' := reserved. + + The file IDs used by default are: + '1F00' EF.SSD (security service descriptor). [o,o] + '2F02' EF.GDO (global data objects) [m,m] + 'A000' EF.PROT (signature log). Cyclic file with 20 records of 53 byte. + Read and update after user authentication. [o,o] + 'B000' EF.PK.RCA.DS (public keys of Root-CA). Size is 512b or size + of keys. [m (unless a 'C00E' is present),m] + 'B001' EF.PK.CA.DS (public keys of CAs). Size is 512b or size + of keys. [o,o] + 'C00n' EF.C.CH.DS (digital signature certificate of card holder) + with n := 0 .. 7. Size is 2k or size of cert. Read and + update allowed after user authentication. [m,m] + 'C00m' EF.C.CA.DS (digital signature certificate of CA) + with m := 8 .. E. Size is 1k or size of cert. Read always + allowed, update after user authentication. [o,o] + 'C100' EF.C.ICC.AUT (AUT certificate of ICC) [o,m] + 'C108' EF.C.CA.AUT (AUT certificate of CA) [o,m] + 'D000' EF.DM (display message) [-,m] + + The letters in brackets indicate optional or mandatory files: The + first for card terminals under full control and the second for + "business" card terminals. +*/ + + + + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> + +#include "scdaemon.h" + +#include "../common/i18n.h" +#include "iso7816.h" +#include "../common/tlv.h" + + +static gpg_error_t +do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + char ct_buf[100], id_buf[100]; + char hexkeygrip[41]; + size_t len, certoff; + unsigned char *der; + size_t derlen; + ksba_cert_t cert; + int fid; + + (void)flags; + + /* Return the certificate of the card holder. */ + fid = 0xC000; + len = app_help_read_length_of_cert (app->slot, fid, &certoff); + if (!len) + return 0; /* Card has not been personalized. */ + + sprintf (ct_buf, "%d", 101); + sprintf (id_buf, "DINSIG.%04X", fid); + send_status_info (ctrl, "CERTINFO", + ct_buf, strlen (ct_buf), + id_buf, strlen (id_buf), + NULL, (size_t)0); + + /* Now we need to read the certificate, so that we can get the + public key out of it. */ + err = iso7816_read_binary (app->slot, certoff, len-certoff, &der, &derlen); + if (err) + { + log_info ("error reading entire certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return 0; + } + + err = ksba_cert_new (&cert); + if (err) + { + xfree (der); + return err; + } + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); der = NULL; + if (err) + { + log_error ("failed to parse the certificate at FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + ksba_cert_release (cert); + return err; + } + err = app_help_get_keygrip_string (cert, hexkeygrip, NULL, NULL); + if (err) + { + log_error ("failed to calculate the keygrip for FID 0x%04X\n", fid); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + ksba_cert_release (cert); + + sprintf (id_buf, "DINSIG.%04X", fid); + send_status_info (ctrl, "KEYPAIRINFO", + hexkeygrip, 40, + id_buf, strlen (id_buf), + NULL, (size_t)0); + return 0; +} + + + + +/* Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer put into CERT and the length of the certificate put into + CERTLEN. + + FIXME: This needs some cleanups and caching with do_learn_status. +*/ +static gpg_error_t +do_readcert (app_t app, const char *certid, + unsigned char **cert, size_t *certlen) +{ + int fid; + gpg_error_t err; + unsigned char *buffer; + const unsigned char *p; + size_t buflen, n; + int class, tag, constructed, ndef; + size_t totobjlen, objlen, hdrlen; + int rootca = 0; + + *cert = NULL; + *certlen = 0; + if (strncmp (certid, "DINSIG.", 7) ) + return gpg_error (GPG_ERR_INV_ID); + certid += 7; + if (!hexdigitp (certid) || !hexdigitp (certid+1) + || !hexdigitp (certid+2) || !hexdigitp (certid+3) + || certid[4]) + return gpg_error (GPG_ERR_INV_ID); + fid = xtoi_4 (certid); + if (fid != 0xC000 ) + return gpg_error (GPG_ERR_NOT_FOUND); + + /* Read the entire file. fixme: This could be optimized by first + reading the header to figure out how long the certificate + actually is. */ + err = iso7816_select_file (app->slot, fid, 0); + if (err) + { + log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err)); + return err; + } + + err = iso7816_read_binary (app->slot, 0, 0, &buffer, &buflen); + if (err) + { + log_error ("error reading certificate from FID 0x%04X: %s\n", + fid, gpg_strerror (err)); + return err; + } + + if (!buflen || *buffer == 0xff) + { + log_info ("no certificate contained in FID 0x%04X\n", fid); + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + /* Now figure something out about the object. */ + 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 ) + ; + else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) + rootca = 1; + else + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (totobjlen <= buflen); + + err = parse_ber_header (&p, &n, &class, &tag, &constructed, + &ndef, &objlen, &hdrlen); + if (err) + goto leave; + + if (rootca) + ; + else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) + { + const unsigned char *save_p; + + /* 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) ) + return gpg_error (GPG_ERR_INV_OBJ); + totobjlen = objlen + hdrlen; + assert (save_p + totobjlen <= buffer + buflen); + memmove (buffer, save_p, totobjlen); + } + + *cert = buffer; + buffer = NULL; + *certlen = totobjlen; + + leave: + xfree (buffer); + return err; +} + + +/* Verify the PIN if required. */ +static gpg_error_t +verify_pin (app_t app, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + const char *s; + int rc; + pininfo_t pininfo; + + if ( app->did_chv1 && !app->force_chv1 ) + return 0; /* No need to verify it again. */ + + memset (&pininfo, 0, sizeof pininfo); + pininfo.fixedlen = -1; + pininfo.minlen = 6; + pininfo.maxlen = 8; + + if (!opt.disable_pinpad + && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) + { + rc = pincb (pincb_arg, + _("||Please enter your PIN at the reader's pinpad"), + NULL); + if (rc) + { + log_info (_("PIN callback returned error: %s\n"), + gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify_kp (app->slot, 0x81, &pininfo); + /* Dismiss the prompt. */ + pincb (pincb_arg, NULL, NULL); + } + else /* No Pinpad. */ + { + char *pinvalue; + + rc = pincb (pincb_arg, "PIN", &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + /* We require the PIN to be at least 6 and at max 8 bytes. + According to the specs, this should all be ASCII. */ + for (s=pinvalue; digitp (s); s++) + ; + if (*s) + { + log_error ("Non-numeric digits found in PIN\n"); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + if (strlen (pinvalue) < pininfo.minlen) + { + log_error ("PIN is too short; minimum length is %d\n", + pininfo.minlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + else if (strlen (pinvalue) > pininfo.maxlen) + { + log_error ("PIN is too large; maximum length is %d\n", + pininfo.maxlen); + xfree (pinvalue); + return gpg_error (GPG_ERR_BAD_PIN); + } + + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + if (gpg_err_code (rc) == GPG_ERR_INV_VALUE) + { + /* We assume that ISO 9564-1 encoding is used and we failed + because the first nibble we passed was 3 and not 2. DIN + says something about looking up such an encoding in the + SSD but I was not able to find any tag relevant to + this. */ + char paddedpin[8]; + int i, ndigits; + + for (ndigits=0, s=pinvalue; *s; ndigits++, s++) + ; + i = 0; + paddedpin[i++] = 0x20 | (ndigits & 0x0f); + for (s=pinvalue; i < sizeof paddedpin && *s && s[1]; s = s+2 ) + paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); + if (i < sizeof paddedpin && *s) + paddedpin[i++] = (((*s - '0') << 4) | 0x0f); + while (i < sizeof paddedpin) + paddedpin[i++] = 0xff; + rc = iso7816_verify (app->slot, 0x81, paddedpin, sizeof paddedpin); + } + xfree (pinvalue); + } + + if (rc) + { + log_error ("verify PIN failed\n"); + return rc; + } + app->did_chv1 = 1; + 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 gpg_error_t +do_sign (app_t app, ctrl_t ctrl, 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 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 }; + static unsigned char sha256_prefix[19] = /* OID is 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 }; + int rc; + int fid; + unsigned char data[19+32]; /* Must be large enough for a SHA-256 digest + + the largest OID _prefix above. */ + int datalen; + + (void)ctrl; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen != 20 && indatalen != 16 && indatalen != 32 + && indatalen != (15+20) && indatalen != (19+32)) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check that the provided ID is vaid. This is not really needed + but we do it to enforce correct usage by the caller. */ + if (strncmp (keyidstr, "DINSIG.", 7) ) + return gpg_error (GPG_ERR_INV_ID); + keyidstr += 7; + 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); + if (fid != 0xC000) + return gpg_error (GPG_ERR_NOT_FOUND); + + /* Prepare the DER object from INDATA. */ + datalen = 35; + if (indatalen == 15+20) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. Check that it is what 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 (indatalen == 19+32) + { + /* Alright, the caller was so kind to send us an already + prepared DER object. Check that it is what we want and that + it matches the hash algorithm. */ + datalen = indatalen; + if (hashalgo == GCRY_MD_SHA256 && !memcmp (indata, sha256_prefix, 19)) + ; + else if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha256_prefix, 19)) + { + /* Fixme: This is a kludge. A better solution is not to use + SHA1 as default but use an autodetection. However this + needs changes in all app-*.c */ + datalen = indatalen; + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data, indata, indatalen); + } + else + { + int len = 15; + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, len); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, len); + else if (hashalgo == GCRY_MD_SHA256) + { + len = 19; + datalen = len + indatalen; + memcpy (data, sha256_prefix, len); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+len, indata, indatalen); + } + + rc = verify_pin (app, pincb, pincb_arg); + if (!rc) + rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0, + outdata, outdatalen); + return rc; +} + + +#if 0 +#warning test function - works but may brick your card +/* Handle the PASSWD command. CHVNOSTR is currently ignored; we + always use VHV0. RESET_MODE is not yet implemented. */ +static gpg_error_t +do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + char *pinvalue; + const char *oldpin; + size_t oldpinlen; + + if ((flags & APP_CHANGE_FLAG_RESET)) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + if ((flags & APP_CHANGE_FLAG_NULLPIN)) + { + /* With the nullpin flag, we do not verify the PIN - it would fail + if the Nullpin is still set. */ + oldpin = "\0\0\0\0\0"; + oldpinlen = 6; + } + else + { + err = verify_pin (app, pincb, pincb_arg); + if (err) + return err; + oldpin = NULL; + oldpinlen = 0; + } + + /* TRANSLATORS: Do not translate the "|*|" prefixes but + keep it at the start of the string. We need this elsewhere + to get some infos on the string. */ + err = pincb (pincb_arg, _("|N|Initial New PIN"), &pinvalue); + if (err) + { + log_error (_("error getting new PIN: %s\n"), gpg_strerror (err)); + return err; + } + + err = iso7816_change_reference_data (app->slot, 0x81, + oldpin, oldpinlen, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + return err; +} +#endif /*0*/ + + +/* Select the DINSIG application on the card in SLOT. This function + must be used before any other DINSIG application functions. */ +gpg_error_t +app_select_dinsig (app_t app) +{ + static char const aid[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; + int slot = app->slot; + int rc; + + rc = iso7816_select_application (slot, aid, sizeof aid, 0); + if (!rc) + { + app->apptype = APPTYPE_DINSIG; + + app->fnc.learn_status = do_learn_status; + app->fnc.readcert = do_readcert; + app->fnc.getattr = NULL; + app->fnc.setattr = NULL; + app->fnc.genkey = NULL; + app->fnc.sign = do_sign; + app->fnc.auth = NULL; + app->fnc.decipher = NULL; + app->fnc.change_pin = NULL /*do_change_pin*/; + app->fnc.check_pin = NULL; + + app->force_chv1 = 1; + } + + return rc; +} |