diff options
Diffstat (limited to '')
-rw-r--r-- | sm/certreqgen.c | 1393 |
1 files changed, 1393 insertions, 0 deletions
diff --git a/sm/certreqgen.c b/sm/certreqgen.c new file mode 100644 index 0000000..92d6ffe --- /dev/null +++ b/sm/certreqgen.c @@ -0,0 +1,1393 @@ +/* certreqgen.c - Generate a key and a certification [request] + * Copyright (C) 2002, 2003, 2005, 2007, 2010, + * 2011 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 format of the parameter file is described in the manual under + "Unattended Usage". + + Here is an example: + $ cat >foo <<EOF + %echo Generating a standard key + Key-Type: RSA + Key-Length: 3072 + Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Ddorf,C=DE + Name-Email: joe@foo.bar + # Do a commit here, so that we can later print a "done" + %commit + %echo done + EOF + + This parameter file was used to create the STEED CA: + Key-Type: RSA + Key-Length: 1024 + Key-Grip: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + Key-Usage: cert + Serial: 1 + Name-DN: CN=The STEED Self-Signing Nonthority + Not-Before: 2011-11-11 + Not-After: 2106-02-06 + Subject-Key-Id: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + Extension: 2.5.29.19 c 30060101ff020101 + Extension: 1.3.6.1.4.1.11591.2.2.2 n 0101ff + Signing-Key: 68A638998DFABAC510EA645CE34F9686B2EDF7EA + %commit + +*/ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "../common/i18n.h" + + +enum para_name + { + pKEYTYPE, + pKEYLENGTH, + pKEYGRIP, + pKEYUSAGE, + pNAMEDN, + pNAMEEMAIL, + pNAMEDNS, + pNAMEURI, + pSERIAL, + pISSUERDN, + pNOTBEFORE, + pNOTAFTER, + pSIGNINGKEY, + pHASHALGO, + pAUTHKEYID, + pSUBJKEYID, + pEXTENSION + }; + +struct para_data_s +{ + struct para_data_s *next; + int lnr; + enum para_name key; + union { + unsigned int usage; + char value[1]; + } u; +}; + +struct reqgen_ctrl_s +{ + int lnr; + int dryrun; +}; + + +static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; +static const char oidstr_subjectKeyIdentifier[] = "2.5.29.14"; +static const char oidstr_keyUsage[] = "2.5.29.15"; +static const char oidstr_basicConstraints[] = "2.5.29.19"; +static const char oidstr_standaloneCertificate[] = "1.3.6.1.4.1.11591.2.2.1"; + + +static int proc_parameters (ctrl_t ctrl, + struct para_data_s *para, + estream_t out_fp, + struct reqgen_ctrl_s *outctrl); +static int create_request (ctrl_t ctrl, + struct para_data_s *para, + const char *carddirect, + ksba_const_sexp_t public, + ksba_const_sexp_t sigkey, + ksba_writer_t writer); + + + +static void +release_parameter_list (struct para_data_s *r) +{ + struct para_data_s *r2; + + for (; r ; r = r2) + { + r2 = r->next; + xfree(r); + } +} + +static struct para_data_s * +get_parameter (struct para_data_s *para, enum para_name key, int seq) +{ + struct para_data_s *r; + + for (r = para; r ; r = r->next) + if ( r->key == key && !seq--) + return r; + return NULL; +} + +static const char * +get_parameter_value (struct para_data_s *para, enum para_name key, int seq) +{ + struct para_data_s *r = get_parameter (para, key, seq); + return (r && *r->u.value)? r->u.value : NULL; +} + +static int +get_parameter_algo (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + if (!r) + return -1; + if (digitp (r->u.value)) + return atoi( r->u.value ); + return gcry_pk_map_name (r->u.value); +} + +/* Parse the usage parameter. Returns 0 on success. Note that we + only care about sign and encrypt and don't (yet) allow all the + other X.509 usage to be specified; instead we will use a fixed + mapping to the X.509 usage flags. */ +static int +parse_parameter_usage (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + char *p, *pn; + unsigned int use; + + if (!r) + return 0; /* none (this is an optional parameter)*/ + + use = 0; + pn = r->u.value; + while ( (p = strsep (&pn, " \t,")) ) + { + if (!*p) + ; + else if ( !ascii_strcasecmp (p, "sign") ) + use |= GCRY_PK_USAGE_SIGN; + else if ( !ascii_strcasecmp (p, "encrypt") + || !ascii_strcasecmp (p, "encr") ) + use |= GCRY_PK_USAGE_ENCR; + else if ( !ascii_strcasecmp (p, "cert") ) + use |= GCRY_PK_USAGE_CERT; + else + { + log_error ("line %d: invalid usage list\n", r?r->lnr:0); + return -1; /* error */ + } + } + r->u.usage = use; + return 0; +} + + +static unsigned int +get_parameter_uint (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key, 0); + + if (!r) + return 0; + + if (r->key == pKEYUSAGE) + return r->u.usage; + + return (unsigned int)strtoul (r->u.value, NULL, 10); +} + + + +/* Read the certificate generation parameters from FP and generate + (all) certificate requests. */ +static int +read_parameters (ctrl_t ctrl, estream_t fp, estream_t out_fp) +{ + static struct { + const char *name; + enum para_name key; + int allow_dups; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Grip", pKEYGRIP }, + { "Key-Usage", pKEYUSAGE }, + { "Name-DN", pNAMEDN }, + { "Name-Email", pNAMEEMAIL, 1 }, + { "Name-DNS", pNAMEDNS, 1 }, + { "Name-URI", pNAMEURI, 1 }, + { "Serial", pSERIAL }, + { "Issuer-DN", pISSUERDN }, + { "Creation-Date", pNOTBEFORE }, + { "Not-Before", pNOTBEFORE }, + { "Expire-Date", pNOTAFTER }, + { "Not-After", pNOTAFTER }, + { "Signing-Key", pSIGNINGKEY }, + { "Hash-Algo", pHASHALGO }, + { "Authority-Key-Id", pAUTHKEYID }, + { "Subject-Key-Id", pSUBJKEYID }, + { "Extension", pEXTENSION, 1 }, + { NULL, 0 } + }; + char line[1024], *p; + const char *err = NULL; + struct para_data_s *para, *r; + int i, rc = 0, any = 0; + struct reqgen_ctrl_s outctrl; + + memset (&outctrl, 0, sizeof (outctrl)); + + err = NULL; + para = NULL; + while (es_fgets (line, DIM(line)-1, fp) ) + { + char *keyword, *value; + + outctrl.lnr++; + if (*line && line[strlen(line)-1] != '\n') + { + err = "line too long"; + break; + } + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + keyword = p; + if (*keyword == '%') + { + for (; *p && !ascii_isspace (*p); p++) + ; + if (*p) + *p++ = 0; + for (; ascii_isspace (*p); p++) + ; + value = p; + trim_trailing_spaces (value); + + if (!ascii_strcasecmp (keyword, "%echo")) + log_info ("%s\n", value); + else if (!ascii_strcasecmp (keyword, "%dry-run")) + outctrl.dryrun = 1; + else if (!ascii_strcasecmp( keyword, "%commit")) + { + rc = proc_parameters (ctrl, para, out_fp, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + log_info ("skipping control '%s' (%s)\n", keyword, value); + + continue; + } + + + if (!(p = strchr (p, ':')) || p == keyword) + { + err = "missing colon"; + break; + } + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = "missing argument"; + break; + } + value = p; + trim_trailing_spaces (value); + + for (i=0; (keywords[i].name + && ascii_strcasecmp (keywords[i].name, keyword)); i++) + ; + if (!keywords[i].name) + { + err = "unknown keyword"; + break; + } + if (keywords[i].key != pKEYTYPE && !para) + { + err = "parameter block does not start with \"Key-Type\""; + break; + } + + if (keywords[i].key == pKEYTYPE && para) + { + rc = proc_parameters (ctrl, para, out_fp, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else if (!keywords[i].allow_dups) + { + for (r = para; r && r->key != keywords[i].key; r = r->next) + ; + if (r) + { + err = "duplicate keyword"; + break; + } + } + + r = xtrycalloc (1, sizeof *r + strlen( value )); + if (!r) + { + err = "out of core"; + break; + } + r->lnr = outctrl.lnr; + r->key = keywords[i].key; + strcpy (r->u.value, value); + r->next = para; + para = r; + } + + if (err) + { + log_error ("line %d: %s\n", outctrl.lnr, err); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (es_ferror(fp)) + { + log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) ); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (para) + { + rc = proc_parameters (ctrl, para, out_fp, &outctrl); + if (rc) + goto leave; + any = 1; + } + + if (!rc && !any) + rc = gpg_error (GPG_ERR_NO_DATA); + + leave: + release_parameter_list (para); + return rc; +} + +/* check whether there are invalid characters in the email address S */ +static int +has_invalid_email_chars (const char *s) +{ + int at_seen=0; + static char valid_chars[] = "01234567890_-." + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (; *s; s++) + { + if (*s & 0x80) + return 1; + if (*s == '@') + at_seen++; + else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+')) + return 1; + else if (at_seen && !strchr (valid_chars, *s)) + return 1; + } + return at_seen != 1; +} + + +/* Check that all required parameters are given and perform the action */ +static int +proc_parameters (ctrl_t ctrl, struct para_data_s *para, + estream_t out_fp, struct reqgen_ctrl_s *outctrl) +{ + gpg_error_t err; + struct para_data_s *r; + const char *s, *string; + int i; + unsigned int nbits; + char numbuf[20]; + unsigned char keyparms[100]; + int rc = 0; + ksba_sexp_t public = NULL; + ksba_sexp_t sigkey = NULL; + int seq; + size_t erroff, errlen; + char *cardkeyid = NULL; + + /* Check that we have all required parameters; */ + assert (get_parameter (para, pKEYTYPE, 0)); + + /* We can only use RSA for now. There is a problem with pkcs-10 on + how to use ElGamal because it is expected that a PK algorithm can + always be used for signing. Another problem is that on-card + generated encryption keys may not be used for signing. */ + i = get_parameter_algo (para, pKEYTYPE); + if (!i && (s = get_parameter_value (para, pKEYTYPE, 0)) && *s) + { + /* Hack to allow creation of certificates directly from a smart + card. For example: "Key-Type: card:OPENPGP.3". */ + if (!strncmp (s, "card:", 5) && s[5]) + cardkeyid = xtrystrdup (s+5); + } + if ( (i < 1 || i != GCRY_PK_RSA) && !cardkeyid ) + { + r = get_parameter (para, pKEYTYPE, 0); + if (r) + log_error (_("line %d: invalid algorithm\n"), r?r->lnr:0); + else + log_error ("No Key-Type specified\n"); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check the keylength. NOTE: If you change this make sure that it + macthes the gpgconflist item in gpgsm.c */ + if (!get_parameter (para, pKEYLENGTH, 0)) + nbits = 3072; + else + nbits = get_parameter_uint (para, pKEYLENGTH); + if ((nbits < 1024 || nbits > 4096) && !cardkeyid) + { + /* The BSI specs dated 2002-11-25 don't allow lengths below 1024. */ + r = get_parameter (para, pKEYLENGTH, 0); + log_error (_("line %d: invalid key length %u (valid are %d to %d)\n"), + r?r->lnr:0, nbits, 1024, 4096); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check the usage. */ + if (parse_parameter_usage (para, pKEYUSAGE)) + { + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check that there is a subject name and that this DN fits our + requirements. */ + if (!(s=get_parameter_value (para, pNAMEDN, 0))) + { + r = get_parameter (para, pNAMEDN, 0); + log_error (_("line %d: no subject name given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + err = ksba_dn_teststr (s, 0, &erroff, &errlen); + if (err) + { + r = get_parameter (para, pNAMEDN, 0); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME) + log_error (_("line %d: invalid subject name label '%.*s'\n"), + r?r->lnr:0, (int)errlen, s+erroff); + else + log_error (_("line %d: invalid subject name '%s' at pos %d\n"), + r?r->lnr:0, s, (int)erroff); + + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Check that the optional email address is okay. */ + for (seq=0; (s=get_parameter_value (para, pNAMEEMAIL, seq)); seq++) + { + if (has_invalid_email_chars (s) + || *s == '@' + || s[strlen(s)-1] == '@' + || s[strlen(s)-1] == '.' + || strstr(s, "..")) + { + r = get_parameter (para, pNAMEEMAIL, seq); + log_error (_("line %d: not a valid email address\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional serial number. */ + string = get_parameter_value (para, pSERIAL, 0); + if (string) + { + if (!strcmp (string, "random")) + ; /* Okay. */ + else + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s) + { + r = get_parameter (para, pSERIAL, 0); + log_error (_("line %d: invalid serial number\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + } + + /* Check the optional issuer DN. */ + string = get_parameter_value (para, pISSUERDN, 0); + if (string) + { + err = ksba_dn_teststr (string, 0, &erroff, &errlen); + if (err) + { + r = get_parameter (para, pISSUERDN, 0); + if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME) + log_error (_("line %d: invalid issuer name label '%.*s'\n"), + r?r->lnr:0, (int)errlen, string+erroff); + else + log_error (_("line %d: invalid issuer name '%s' at pos %d\n"), + r?r->lnr:0, string, (int)erroff); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional creation date. */ + string = get_parameter_value (para, pNOTBEFORE, 0); + if (string && !string2isotime (NULL, string)) + { + r = get_parameter (para, pNOTBEFORE, 0); + log_error (_("line %d: invalid date given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + + /* Check the optional expire date. */ + string = get_parameter_value (para, pNOTAFTER, 0); + if (string && !string2isotime (NULL, string)) + { + r = get_parameter (para, pNOTAFTER, 0); + log_error (_("line %d: invalid date given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* Get the optional signing key. */ + string = get_parameter_value (para, pSIGNINGKEY, 0); + if (string) + { + rc = gpgsm_agent_readkey (ctrl, 0, string, &sigkey); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error getting signing key by keygrip '%s'" + ": %s\n"), r?r->lnr:0, s, gpg_strerror (rc)); + xfree (cardkeyid); + return rc; + } + } + + /* Check the optional hash-algo. */ + { + int mdalgo; + + string = get_parameter_value (para, pHASHALGO, 0); + if (string && !((mdalgo = gcry_md_map_name (string)) + && (mdalgo == GCRY_MD_SHA1 + || mdalgo == GCRY_MD_SHA256 + || mdalgo == GCRY_MD_SHA384 + || mdalgo == GCRY_MD_SHA512))) + { + r = get_parameter (para, pHASHALGO, 0); + log_error (_("line %d: invalid hash algorithm given\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional AuthorityKeyId. */ + string = get_parameter_value (para, pAUTHKEYID, 0); + if (string) + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s || (i&1)) + { + r = get_parameter (para, pAUTHKEYID, 0); + log_error (_("line %d: invalid authority-key-id\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional SubjectKeyId. */ + string = get_parameter_value (para, pSUBJKEYID, 0); + if (string) + { + for (s=string, i=0; hexdigitp (s); s++, i++) + ; + if (*s || (i&1)) + { + r = get_parameter (para, pSUBJKEYID, 0); + log_error (_("line %d: invalid subject-key-id\n"), r?r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Check the optional extensions. */ + for (seq=0; (string=get_parameter_value (para, pEXTENSION, seq)); seq++) + { + int okay = 0; + + s = strpbrk (string, " \t:"); + if (s) + { + s++; + while (spacep (s)) + s++; + if (*s && strchr ("nNcC", *s)) + { + s++; + while (spacep (s)) + s++; + if (*s == ':') + s++; + if (*s) + { + while (spacep (s)) + s++; + for (i=0; hexdigitp (s); s++, i++) + ; + if (!((*s && *s != ':') || !i || (i&1))) + okay = 1; + } + } + } + if (!okay) + { + r = get_parameter (para, pEXTENSION, seq); + log_error (_("line %d: invalid extension syntax\n"), r? r->lnr:0); + xfree (cardkeyid); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + /* Create or retrieve the public key. */ + if (cardkeyid) /* Take the key from the current smart card. */ + { + rc = gpgsm_agent_readkey (ctrl, 1, cardkeyid, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error reading key '%s' from card: %s\n"), + r?r->lnr:0, cardkeyid, gpg_strerror (rc)); + xfree (sigkey); + xfree (cardkeyid); + return rc; + } + } + else if ((s=get_parameter_value (para, pKEYGRIP, 0))) /* Use existing key.*/ + { + rc = gpgsm_agent_readkey (ctrl, 0, s, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: error getting key by keygrip '%s': %s\n"), + r->lnr, s, gpg_strerror (rc)); + xfree (sigkey); + xfree (cardkeyid); + return rc; + } + } + else if (!outctrl->dryrun) /* Generate new key. */ + { + sprintf (numbuf, "%u", nbits); + snprintf ((char*)keyparms, DIM (keyparms), + "(6:genkey(3:rsa(5:nbits%d:%s)))", + (int)strlen (numbuf), numbuf); + rc = gpgsm_agent_genkey (ctrl, keyparms, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE, 0); + log_error (_("line %d: key generation failed: %s <%s>\n"), + r?r->lnr:0, gpg_strerror (rc), gpg_strsource (rc)); + xfree (sigkey); + xfree (cardkeyid); + return rc; + } + } + + + if (!outctrl->dryrun) + { + gnupg_ksba_io_t b64writer = NULL; + ksba_writer_t writer; + int create_cert ; + + create_cert = !!get_parameter_value (para, pSERIAL, 0); + + ctrl->pem_name = create_cert? "CERTIFICATE" : "CERTIFICATE REQUEST"; + + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &writer); + if (rc) + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + else + { + rc = create_request (ctrl, para, cardkeyid, public, sigkey, writer); + if (!rc) + { + rc = gnupg_ksba_finish_writer (b64writer); + if (rc) + log_error ("write failed: %s\n", gpg_strerror (rc)); + else + { + gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); + log_info ("certificate%s created\n", + create_cert?"":" request"); + } + } + gnupg_ksba_destroy_writer (b64writer); + } + } + + xfree (sigkey); + xfree (public); + xfree (cardkeyid); + + return rc; +} + + +/* Parameters are checked, the key pair has been created. Now + generate the request and write it out */ +static int +create_request (ctrl_t ctrl, + struct para_data_s *para, + const char *carddirect, + ksba_const_sexp_t public, + ksba_const_sexp_t sigkey, + ksba_writer_t writer) +{ + ksba_certreq_t cr; + gpg_error_t err; + gcry_md_hd_t md; + ksba_stop_reason_t stopreason; + int rc = 0; + const char *s, *string; + unsigned int use; + int seq; + char *buf, *p; + size_t len; + char numbuf[30]; + ksba_isotime_t atime; + int certmode = 0; + int mdalgo; + + err = ksba_certreq_new (&cr); + if (err) + return err; + + string = get_parameter_value (para, pHASHALGO, 0); + if (string) + mdalgo = gcry_md_map_name (string); + else + mdalgo = GCRY_MD_SHA256; + rc = gcry_md_open (&md, mdalgo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_debug (md, "cr.cri"); + + ksba_certreq_set_hash_function (cr, HASH_FNC, md); + ksba_certreq_set_writer (cr, writer); + + err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN, 0)); + if (err) + { + log_error ("error setting the subject's name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + for (seq=0; (s = get_parameter_value (para, pNAMEEMAIL, seq)); seq++) + { + buf = xtrymalloc (strlen (s) + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + *buf = '<'; + strcpy (buf+1, s); + strcat (buf+1, ">"); + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + for (seq=0; (s = get_parameter_value (para, pNAMEDNS, seq)); seq++) + { + len = strlen (s); + assert (len); + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (11 + strlen (numbuf) + len + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + p = stpcpy (p, "(8:dns-name"); + p = stpcpy (p, numbuf); + p = stpcpy (p, s); + strcpy (p, ")"); + + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + for (seq=0; (s = get_parameter_value (para, pNAMEURI, seq)); seq++) + { + len = strlen (s); + assert (len); + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (6 + strlen (numbuf) + len + 3); + if (!buf) + { + rc = out_of_core (); + goto leave; + } + p = stpcpy (p, "(3:uri"); + p = stpcpy (p, numbuf); + p = stpcpy (p, s); + strcpy (p, ")"); + + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + + err = ksba_certreq_set_public_key (cr, public); + if (err) + { + log_error ("error setting the public key: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + + /* Set key usage flags. */ + use = get_parameter_uint (para, pKEYUSAGE); + if (use) + { + unsigned int mask, pos; + unsigned char der[4]; + + der[0] = 0x03; + der[1] = 0x02; + der[2] = 0; + der[3] = 0; + if ((use & GCRY_PK_USAGE_SIGN)) + { + /* For signing only we encode the bits: + KSBA_KEYUSAGE_DIGITAL_SIGNATURE + KSBA_KEYUSAGE_NON_REPUDIATION = 0b11 -> 0b11000000 */ + der[3] |= 0xc0; + } + if ((use & GCRY_PK_USAGE_ENCR)) + { + /* For encrypt only we encode the bits: + KSBA_KEYUSAGE_KEY_ENCIPHERMENT + KSBA_KEYUSAGE_DATA_ENCIPHERMENT = 0b1100 -> 0b00110000 */ + der[3] |= 0x30; + } + if ((use & GCRY_PK_USAGE_CERT)) + { + /* For certify only we encode the bits: + KSBA_KEYUSAGE_KEY_CERT_SIGN + KSBA_KEYUSAGE_CRL_SIGN = 0b1100000 -> 0b00000110 */ + der[3] |= 0x06; + } + + /* Count number of unused bits. */ + for (mask=1, pos=0; pos < 8 * sizeof mask; pos++, mask <<= 1) + { + if ((der[3] & mask)) + break; + der[2]++; + } + + err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, der, 4); + if (err) + { + log_error ("error setting the key usage: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + + /* See whether we want to create an X.509 certificate. */ + string = get_parameter_value (para, pSERIAL, 0); + if (string) + { + certmode = 1; + + /* Store the serial number. */ + if (!strcmp (string, "random")) + { + char snbuf[3+8+1]; + + memcpy (snbuf, "(8:", 3); + gcry_create_nonce (snbuf+3, 8); + /* Clear high bit to guarantee a positive integer. */ + snbuf[3] &= 0x7f; + snbuf[3+8] = ')'; + err = ksba_certreq_set_serial (cr, snbuf); + } + else + { + char *hexbuf; + + /* Allocate a buffer large enough to prefix the string with + a '0' so to have an even number of digits. Prepend two + further '0' so that the binary result will have a leading + 0 byte and thus can't be the representation of a negative + number. Note that ksba_certreq_set_serial strips all + unneeded leading 0 bytes. */ + hexbuf = p = xtrymalloc (2 + 1 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + if ((strlen (string) & 1)) + *p++ = '0'; + *p++ = '0'; + *p++ = '0'; + strcpy (p, string); + for (p=hexbuf, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[len++] = xtoi_2 (p); + /* Now build the S-expression. */ + snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len); + buf = p = xtrymalloc (1 + strlen (numbuf) + len + 1 + 1); + if (!buf) + { + err = gpg_error_from_syserror (); + xfree (hexbuf); + goto leave; + } + p = stpcpy (stpcpy (buf, "("), numbuf); + memcpy (p, hexbuf, len); + p += len; + strcpy (p, ")"); + xfree (hexbuf); + err = ksba_certreq_set_serial (cr, buf); + xfree (buf); + } + if (err) + { + log_error ("error setting the serial number: %s\n", + gpg_strerror (err)); + goto leave; + } + + + /* Store the issuer DN. If no issuer DN is given and no signing + key has been set we add the standalone extension and the + basic constraints to mark it as a self-signed CA + certificate. */ + string = get_parameter_value (para, pISSUERDN, 0); + if (string) + { + /* Issuer DN given. Note that this may be the same as the + subject DN and thus this could as well be a self-signed + certificate. However the caller needs to explicitly + specify basicConstraints and so forth. */ + err = ksba_certreq_set_issuer (cr, string); + if (err) + { + log_error ("error setting the issuer DN: %s\n", + gpg_strerror (err)); + goto leave; + } + + } + else if (!string && !sigkey) + { + /* Self-signed certificate requested. Add basicConstraints + and the custom GnuPG standalone extension. */ + err = ksba_certreq_add_extension (cr, oidstr_basicConstraints, 1, + "\x30\x03\x01\x01\xff", 5); + if (err) + goto leave; + err = ksba_certreq_add_extension (cr, oidstr_standaloneCertificate, 0, + "\x01\x01\xff", 3); + if (err) + goto leave; + } + + /* Store the creation date. */ + string = get_parameter_value (para, pNOTBEFORE, 0); + if (string) + { + if (!string2isotime (atime, string)) + BUG (); /* We already checked the value. */ + } + else + gnupg_get_isotime (atime); + err = ksba_certreq_set_validity (cr, 0, atime); + if (err) + { + log_error ("error setting the creation date: %s\n", + gpg_strerror (err)); + goto leave; + } + + + /* Store the expire date. If it is not given, libksba inserts a + default value. */ + string = get_parameter_value (para, pNOTAFTER, 0); + if (string) + { + if (!string2isotime (atime, string)) + BUG (); /* We already checked the value. */ + err = ksba_certreq_set_validity (cr, 1, atime); + if (err) + { + log_error ("error setting the expire date: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + + /* Figure out the signing algorithm. If no sigkey has been + given we set it to the public key to create a self-signed + certificate. */ + if (!sigkey) + sigkey = public; + + { + unsigned char *siginfo; + + err = transform_sigval (sigkey, + gcry_sexp_canon_len (sigkey, 0, NULL, NULL), + mdalgo, &siginfo, NULL); + if (!err) + { + err = ksba_certreq_set_siginfo (cr, siginfo); + xfree (siginfo); + } + if (err) + { + log_error ("error setting the siginfo: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + + /* Insert the AuthorityKeyId. */ + string = get_parameter_value (para, pAUTHKEYID, 0); + if (string) + { + char *hexbuf; + + /* Allocate a buffer for in-place conversion. We also add 4 + extra bytes space for the tags and lengths fields. */ + hexbuf = xtrymalloc (4 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + strcpy (hexbuf+4, string); + for (p=hexbuf+4, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[4+len++] = xtoi_2 (p); + if (len > 125) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (hexbuf); + goto leave; + } + hexbuf[0] = 0x30; /* Tag for a Sequence. */ + hexbuf[1] = len+2; + hexbuf[2] = 0x80; /* Context tag for an implicit Octet string. */ + hexbuf[3] = len; + err = ksba_certreq_add_extension (cr, oidstr_authorityKeyIdentifier, + 0, + hexbuf, 4+len); + xfree (hexbuf); + if (err) + { + log_error ("error setting the authority-key-id: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + /* Insert the SubjectKeyId. */ + string = get_parameter_value (para, pSUBJKEYID, 0); + if (string) + { + char *hexbuf; + + /* Allocate a buffer for in-place conversion. We also add 2 + extra bytes space for the tag and length field. */ + hexbuf = xtrymalloc (2 + strlen (string) + 1); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + strcpy (hexbuf+2, string); + for (p=hexbuf+2, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[2+len++] = xtoi_2 (p); + if (len > 127) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + xfree (hexbuf); + goto leave; + } + hexbuf[0] = 0x04; /* Tag for an Octet string. */ + hexbuf[1] = len; + err = ksba_certreq_add_extension (cr, oidstr_subjectKeyIdentifier, 0, + hexbuf, 2+len); + xfree (hexbuf); + if (err) + { + log_error ("error setting the subject-key-id: %s\n", + gpg_strerror (err)); + goto leave; + } + } + + /* Insert additional extensions. */ + for (seq=0; (string = get_parameter_value (para, pEXTENSION, seq)); seq++) + { + char *hexbuf; + char *oidstr; + int crit = 0; + + s = strpbrk (string, " \t:"); + if (!s) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + + oidstr = xtrymalloc (s - string + 1); + if (!oidstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (oidstr, string, (s-string)); + oidstr[(s-string)] = 0; + + s++; + while (spacep (s)) + s++; + if (!*s) + { + err = gpg_error (GPG_ERR_INTERNAL); + xfree (oidstr); + goto leave; + } + + if (strchr ("cC", *s)) + crit = 1; + s++; + while (spacep (s)) + s++; + if (*s == ':') + s++; + while (spacep (s)) + s++; + + hexbuf = xtrystrdup (s); + if (!hexbuf) + { + err = gpg_error_from_syserror (); + xfree (oidstr); + goto leave; + } + for (p=hexbuf, len=0; p[0] && p[1]; p += 2) + ((unsigned char*)hexbuf)[len++] = xtoi_2 (p); + err = ksba_certreq_add_extension (cr, oidstr, crit, + hexbuf, len); + xfree (oidstr); + xfree (hexbuf); + } + } + else + sigkey = public; + + do + { + err = ksba_certreq_build (cr, &stopreason); + if (err) + { + log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err)); + rc = err; + goto leave; + } + if (stopreason == KSBA_SR_NEED_SIG) + { + gcry_sexp_t s_pkey; + size_t n; + unsigned char grip[20]; + char hexgrip[41]; + unsigned char *sigval, *newsigval; + size_t siglen; + + n = gcry_sexp_canon_len (sigkey, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)sigkey, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if ( !gcry_pk_get_keygrip (s_pkey, grip) ) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't figure out the keygrip\n"); + gcry_sexp_release (s_pkey); + goto leave; + } + gcry_sexp_release (s_pkey); + bin2hex (grip, 20, hexgrip); + + log_info ("about to sign the %s for key: &%s\n", + certmode? "certificate":"CSR", hexgrip); + + if (carddirect && !certmode) + rc = gpgsm_scd_pksign (ctrl, carddirect, NULL, + gcry_md_read (md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), + mdalgo, + &sigval, &siglen); + else + { + char *orig_codeset; + char *desc; + + orig_codeset = i18n_switchto_utf8 (); + desc = percent_plus_escape + (_("To complete this certificate request please enter" + " the passphrase for the key you just created once" + " more.\n")); + i18n_switchback (orig_codeset); + rc = gpgsm_agent_pksign (ctrl, hexgrip, desc, + gcry_md_read(md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), + mdalgo, + &sigval, &siglen); + xfree (desc); + } + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = transform_sigval (sigval, siglen, mdalgo, + &newsigval, NULL); + xfree (sigval); + if (!err) + { + err = ksba_certreq_set_sig_val (cr, newsigval); + xfree (newsigval); + } + if (err) + { + log_error ("failed to store the sig_val: %s\n", + gpg_strerror (err)); + rc = err; + goto leave; + } + } + } + while (stopreason != KSBA_SR_READY); + + + leave: + gcry_md_close (md); + ksba_certreq_release (cr); + return rc; +} + + + +/* Create a new key by reading the parameters from IN_FP. Multiple + keys may be created */ +int +gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream) +{ + int rc; + + rc = read_parameters (ctrl, in_stream, out_stream); + if (rc) + { + log_error ("error creating certificate request: %s <%s>\n", + gpg_strerror (rc), gpg_strsource (rc)); + goto leave; + } + + leave: + return rc; +} |