diff --git a/agent/genkey.c b/agent/genkey.c index 5c6ae9355..78b5bd5ea 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -1,619 +1,619 @@ /* genkey.c - Generate a keypair * Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH. * * 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 "agent.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" static int store_key (gcry_sexp_t private, const char *passphrase, int force, unsigned long s2k_count, time_t timestamp) { int rc; unsigned char *buf; size_t len; unsigned char grip[20]; if ( !gcry_pk_get_keygrip (private, grip) ) { log_error ("can't calculate keygrip\n"); return gpg_error (GPG_ERR_GENERAL); } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = gcry_malloc_secure (len); if (!buf) return out_of_core (); len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); assert (len); if (passphrase) { unsigned char *p; rc = agent_protect (buf, passphrase, &p, &len, s2k_count, -1); if (rc) { xfree (buf); return rc; } xfree (buf); buf = p; } rc = agent_write_private_key (grip, buf, len, force, timestamp); xfree (buf); return rc; } /* Count the number of non-alpha characters in S. Control characters and non-ascii characters are not considered. */ static size_t nonalpha_count (const char *s) { size_t n; for (n=0; *s; s++) if (isascii (*s) && ( isdigit (*s) || ispunct (*s) )) n++; return n; } /* Check PW against a list of pattern. Return 0 if PW does not match these pattern. */ static int check_passphrase_pattern (ctrl_t ctrl, const char *pw) { gpg_error_t err = 0; const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); FILE *infp; const char *argv[10]; pid_t pid; int result, i; (void)ctrl; infp = gnupg_tmpfile (); if (!infp) { err = gpg_error_from_syserror (); log_error (_("error creating temporary file: %s\n"), gpg_strerror (err)); return 1; /* Error - assume password should not be used. */ } if (fwrite (pw, strlen (pw), 1, infp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing to temporary file: %s\n"), gpg_strerror (err)); fclose (infp); return 1; /* Error - assume password should not be used. */ } fseek (infp, 0, SEEK_SET); clearerr (infp); i = 0; argv[i++] = "--null"; argv[i++] = "--", argv[i++] = opt.check_passphrase_pattern, argv[i] = NULL; assert (i < sizeof argv); if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid)) result = 1; /* Execute error - assume password should no be used. */ else if (gnupg_wait_process (pgmname, pid, 1, NULL)) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ gnupg_release_process (pid); /* Overwrite our temporary file. */ fseek (infp, 0, SEEK_SET); clearerr (infp); for (i=((strlen (pw)+99)/100)*100; i > 0; i--) putc ('\xff', infp); fflush (infp); fclose (infp); return result; } static int take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn) { gpg_error_t err; if (opt.enforce_passphrase_constraints) { err = agent_show_message (ctrl, desc, L_("Enter new passphrase")); if (!err) err = gpg_error (GPG_ERR_CANCELED); } else err = agent_get_confirmation (ctrl, desc, anyway_btn, L_("Enter new passphrase"), 0); return err; } static int take_this_one_anyway (ctrl_t ctrl, const char *desc) { return take_this_one_anyway2 (ctrl, desc, L_("Take this one anyway")); } /* Check whether the passphrase PW is suitable. Returns 0 if the passphrase is suitable and true if it is not and the user should be asked to provide a different one. If FAILED_CONSTRAINT is set, a message describing the problem is returned in *FAILED_CONSTRAINT. */ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int no_empty, char **failed_constraint) { gpg_error_t err = 0; unsigned int minlen = opt.min_passphrase_len; unsigned int minnonalpha = opt.min_passphrase_nonalpha; char *msg1 = NULL; char *msg2 = NULL; char *msg3 = NULL; if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return 0; if (!pw) pw = ""; /* The first check is to warn about an empty passphrase. */ if (!*pw) { const char *desc = (opt.enforce_passphrase_constraints || no_empty? L_("You have not entered a passphrase!%0A" "An empty passphrase is not allowed.") : L_("You have not entered a passphrase - " "this is in general a bad idea!%0A" "Please confirm that you do not want to " "have any protection on your key.")); err = 1; if (failed_constraint) { if (opt.enforce_passphrase_constraints || no_empty) *failed_constraint = xstrdup (desc); else err = take_this_one_anyway2 (ctrl, desc, L_("Yes, protection is not needed")); } goto leave; } /* Now check the constraints and collect the error messages unless in silent mode which returns immediately. */ if (utf8_charcount (pw, -1) < minlen ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg1 = xtryasprintf ( ngettext ("A passphrase should be at least %u character long.", "A passphrase should be at least %u characters long.", minlen), minlen ); if (!msg1) { err = gpg_error_from_syserror (); goto leave; } } if (nonalpha_count (pw) < minnonalpha ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg2 = xtryasprintf ( ngettext ("A passphrase should contain at least %u digit or%%0A" "special character.", "A passphrase should contain at least %u digits or%%0A" "special characters.", minnonalpha), minnonalpha ); if (!msg2) { err = gpg_error_from_syserror (); goto leave; } } /* If configured check the passphrase against a list of known words and pattern. The actual test is done by an external program. The warning message is generic to give the user no hint on how to circumvent this list. */ if (*pw && opt.check_passphrase_pattern && check_passphrase_pattern (ctrl, pw)) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg3 = xtryasprintf (L_("A passphrase may not be a known term or match%%0A" "certain pattern.")); if (!msg3) { err = gpg_error_from_syserror (); goto leave; } } if (failed_constraint && (msg1 || msg2 || msg3)) { char *msg; size_t n; msg = strconcat (L_("Warning: You have entered an insecure passphrase."), "%0A%0A", msg1? msg1 : "", msg1? "%0A" : "", msg2? msg2 : "", msg2? "%0A" : "", msg3? msg3 : "", msg3? "%0A" : "", NULL); if (!msg) { err = gpg_error_from_syserror (); goto leave; } /* Strip a trailing "%0A". */ n = strlen (msg); if (n > 3 && !strcmp (msg + n - 3, "%0A")) msg[n-3] = 0; err = 1; if (opt.enforce_passphrase_constraints) *failed_constraint = msg; else { err = take_this_one_anyway (ctrl, msg); xfree (msg); } } leave: xfree (msg1); xfree (msg2); xfree (msg3); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return gpg_error (GPG_ERR_BAD_PASSPHRASE); } /* Ask the user for a new passphrase using PROMPT. On success the function returns 0 and store the passphrase at R_PASSPHRASE; if the user opted not to use a passphrase NULL will be stored there. The user needs to free the returned string. In case of an error and error code is returned and NULL stored at R_PASSPHRASE. */ gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase) { gpg_error_t err; const char *text1 = prompt; const char *text2 = L_("Please re-enter this passphrase"); char *initial_errtext = NULL; struct pin_entry_info_s *pi, *pi2; *r_passphrase = NULL; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { size_t size; unsigned char *buffer; err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size, MAX_PASSPHRASE_LEN); if (!err) { if (size) { buffer[size] = 0; *r_passphrase = buffer; } else *r_passphrase = NULL; } return err; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); - xfree (pi2); + xfree (pi); return err; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = 0; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0); xfree (initial_errtext); initial_errtext = NULL; if (!err) { if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; goto next_try; } /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = xtrystrdup (L_("does not match - try again")); if (initial_errtext) goto next_try; err = gpg_error_from_syserror (); } } } if (!err && *pi->pin) { /* User wants a passphrase. */ *r_passphrase = xtrystrdup (pi->pin); if (!*r_passphrase) err = gpg_error_from_syserror (); } xfree (initial_errtext); xfree (pi2); xfree (pi); return err; } /* Generate a new keypair according to the parameters given in KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase using the cache nonce. If NO_PROTECTION is true the key will not be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that passphrase will be used for the new key. If TIMESTAMP is not zero it will be recorded as creation date of the key (unless extended format is disabled) . */ int agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparamlen, int no_protection, const char *override_passphrase, int preset, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; char *passphrase_buffer = NULL; const char *passphrase; int rc; size_t len; char *buf; rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen); if (rc) { log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc)); return gpg_error (GPG_ERR_INV_DATA); } /* Get the passphrase now, cause key generation may take a while. */ if (override_passphrase) passphrase = override_passphrase; else if (no_protection || !cache_nonce) passphrase = NULL; else { passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE); passphrase = passphrase_buffer; } if (passphrase || no_protection) ; else { rc = agent_ask_new_passphrase (ctrl, L_("Please enter the passphrase to%0A" "protect your new key"), &passphrase_buffer); if (rc) return rc; passphrase = passphrase_buffer; } rc = gcry_pk_genkey (&s_key, s_keyparam ); gcry_sexp_release (s_keyparam); if (rc) { log_error ("key generation failed: %s\n", gpg_strerror (rc)); xfree (passphrase_buffer); return rc; } /* break out the parts */ s_private = gcry_sexp_find_token (s_key, "private-key", 0); if (!s_private) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } s_public = gcry_sexp_find_token (s_key, "public-key", 0); if (!s_public) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_private); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ if (DBG_CRYPTO) log_debug ("storing private key\n"); rc = store_key (s_private, passphrase, 0, ctrl->s2k_count, timestamp); if (!rc) { if (!cache_nonce) { char tmpbuf[12]; gcry_create_nonce (tmpbuf, 12); cache_nonce = bin2hex (tmpbuf, 12, NULL); } if (cache_nonce && !no_protection && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, ctrl->cache_ttl_opt_preset)) agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL); if (preset && !no_protection) { unsigned char grip[20]; char hexgrip[40+1]; if (gcry_pk_get_keygrip (s_private, grip)) { bin2hex(grip, 20, hexgrip); rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase, ctrl->cache_ttl_opt_preset); } } } xfree (passphrase_buffer); passphrase_buffer = NULL; passphrase = NULL; gcry_sexp_release (s_private); if (rc) { gcry_sexp_release (s_public); return rc; } /* return the public key */ if (DBG_CRYPTO) log_debug ("returning public key\n"); len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { gpg_error_t tmperr = out_of_core (); gcry_sexp_release (s_private); gcry_sexp_release (s_public); return tmperr; } len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); gcry_sexp_release (s_public); xfree (buf); return 0; } /* Apply a new passphrase to the key S_SKEY and store it. If PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered passphrase at that address. */ gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr) { gpg_error_t err; if (passphrase_addr && *passphrase_addr) { /* Take an empty string as request not to protect the key. */ err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1, ctrl->s2k_count, 0); } else { char *pass = NULL; if (passphrase_addr) { xfree (*passphrase_addr); *passphrase_addr = NULL; } err = agent_ask_new_passphrase (ctrl, L_("Please enter the new passphrase"), &pass); if (!err) err = store_key (s_skey, pass, 1, ctrl->s2k_count, 0); if (!err && passphrase_addr) *passphrase_addr = pass; else xfree (pass); } return err; } diff --git a/scd/app-nks.c b/scd/app-nks.c index d7a4dbd36..1f5932183 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -1,1428 +1,1428 @@ /* app-nks.c - The Telesec NKS card application. * Copyright (C) 2004, 2007, 2008, 2009 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 . */ /* Notes: - We are now targeting TCOS 3 cards and it may happen that there is a regression towards TCOS 2 cards. Please report. - The TKS3 AUT key is not used. It seems that it is only useful for the internal authentication command and not accessible by other applications. The key itself is in the encryption class but the corresponding certificate has only the digitalSignature capability. - If required, we automagically switch between the NKS application and the SigG application. This avoids to use the DINSIG application which is somewhat limited, has no support for Secure Messaging as required by TCOS 3 and has no way to change the PIN or even set the NullPIN. - We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer cards. This is because the NKS application has moved to DF02 with TCOS 3 and thus we better use a DF independent tag. - We use only the global PINs for the NKS application. */ #include #include #include #include #include #include #include #include "scdaemon.h" #include "../common/i18n.h" #include "iso7816.h" #include "../common/tlv.h" #include "apdu.h" #include "../common/host2net.h" static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 }; static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 }; static struct { int is_sigg; /* Valid for SigG application. */ int fid; /* File ID. */ int nks_ver; /* 0 for NKS version 2, 3 for version 3. */ int certtype; /* Type of certificate or 0 if it is not a certificate. */ int iskeypair; /* If true has the FID of the corresponding certificate. */ int issignkey; /* True if file is a key usable for signing. */ int isenckey; /* True if file is a key usable for decryption. */ unsigned char kid; /* Corresponding key references. */ } filelist[] = { { 0, 0x4531, 0, 0, 0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */ { 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */ { 0, 0x4331, 0, 100 }, { 0, 0x4332, 0, 100 }, { 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */ { 0, 0x45B1, 0, 0, 0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */ { 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */ { 0, 0x43B1, 0, 100 }, { 0, 0x43B2, 0, 100 }, /* The authentication key is not used. */ /* { 0, 0x4571, 3, 0, 0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */ /* { 0, 0xC500, 3, 101 }, /\* EF_C.NKS.AUT *\/ */ { 0, 0x45B2, 3, 0, 0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */ { 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */ { 1, 0x4531, 3, 0, 0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG */ { 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */ { 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */ { 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */ { 0, 0 } }; /* Object with application (i.e. NKS) specific data. */ struct app_local_s { int nks_version; /* NKS version. */ int sigg_active; /* True if switched to the SigG application. */ int sigg_msig_checked;/* True if we checked for a mass signature card. */ int sigg_is_msig; /* True if this is a mass signature card. */ int need_app_select; /* Need to re-select the application. */ }; static gpg_error_t switch_application (app_t app, int enable_sigg); /* Release local data. */ static void do_deinit (app_t app) { if (app && app->app_local) { xfree (app->app_local); app->app_local = NULL; } } static int all_zero_p (void *buffer, size_t length) { char *p; for (p=buffer; length; length--, p++) if (*p) return 0; return 1; } /* Read the file with FID, assume it contains a public key and return its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */ static gpg_error_t keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr) { gpg_error_t err; unsigned char grip[20]; unsigned char *buffer[2]; size_t buflen[2]; gcry_sexp_t sexp; int i; int offset[2] = { 0, 0 }; err = iso7816_select_file (app->slot, fid, 0); if (err) return err; err = iso7816_read_record (app->slot, 1, 1, 0, &buffer[0], &buflen[0]); if (err) return err; err = iso7816_read_record (app->slot, 2, 1, 0, &buffer[1], &buflen[1]); if (err) { xfree (buffer[0]); return err; } if (app->app_local->nks_version < 3) { /* Old versions of NKS store the values in a TLV encoded format. We need to do some checks. */ for (i=0; i < 2; i++) { /* Check that the value appears like an integer encoded as Simple-TLV. We don't check the tag because the tests cards I have use 1 for both, the modulus and the exponent - the example in the documentation gives 2 for the exponent. */ if (buflen[i] < 3) err = gpg_error (GPG_ERR_TOO_SHORT); else if (buffer[i][1] != buflen[i]-2 ) err = gpg_error (GPG_ERR_INV_OBJ); else offset[i] = 2; } } else { /* Remove leading zeroes to get a correct keygrip. Take care of negative numbers. We should also fix it the same way in libgcrypt but we can't yet rely on it yet. */ for (i=0; i < 2; i++) { while (buflen[i]-offset[i] > 1 && !buffer[i][offset[i]] && !(buffer[i][offset[i]+1] & 0x80)) offset[i]++; } } /* Check whether negative values are not prefixed with a zero and fix that. */ for (i=0; i < 2; i++) { if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80)) { unsigned char *newbuf; size_t newlen; newlen = 1 + buflen[i] - offset[i]; newbuf = xtrymalloc (newlen); - if (!newlen) + if (!newbuf) { xfree (buffer[0]); xfree (buffer[1]); return gpg_error_from_syserror (); } newbuf[0] = 0; memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]); xfree (buffer[i]); buffer[i] = newbuf; buflen[i] = newlen; offset[i] = 0; } } if (!err) err = gcry_sexp_build (&sexp, NULL, "(public-key (rsa (n %b) (e %b)))", (int)buflen[0]-offset[0], buffer[0]+offset[0], (int)buflen[1]-offset[1], buffer[1]+offset[1]); xfree (buffer[0]); xfree (buffer[1]); if (err) return err; if (!gcry_pk_get_keygrip (sexp, grip)) { err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by libgcrypt. */ } else { bin2hex (grip, 20, r_gripstr); } gcry_sexp_release (sexp); return err; } /* TCOS responds to a verify with empty data (i.e. without the Lc * byte) with the status of the PIN. PWID is the PIN ID, If SIGG is * true, the application is switched into SigG mode. Returns: * ISO7816_VERIFY_* codes or non-negative number of verification * attempts left. */ static int get_chv_status (app_t app, int sigg, int pwid) { if (switch_application (app, sigg)) return sigg? -2 : -1; /* No such PIN / General error. */ return iso7816_verify_status (app_get_slot (app), pwid); } /* 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) { static struct { const char *name; int special; } table[] = { { "$AUTHKEYID", 1 }, { "$ENCRKEYID", 2 }, { "$SIGNKEYID", 3 }, { "NKS-VERSION", 4 }, { "CHV-STATUS", 5 }, { NULL, 0 } }; gpg_error_t err = 0; int idx; char buffer[100]; err = switch_application (app, 0); if (err) return err; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); switch (table[idx].special) { case 1: /* $AUTHKEYID */ { /* NetKey 3.0 cards define an authentication key but according to the specs this key is only usable for encryption and not signing. it might work anyway but it has not yet been tested - fixme. Thus for now we use the NKS signature key for authentication. */ char const tmp[] = "NKS-NKS3.4531"; send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); } break; case 2: /* $ENCRKEYID */ { char const tmp[] = "NKS-NKS3.45B1"; send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); } break; case 3: /* $SIGNKEYID */ { char const tmp[] = "NKS-NKS3.4531"; send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); } break; case 4: /* NKS-VERSION */ snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version); send_status_info (ctrl, table[idx].name, buffer, strlen (buffer), NULL, 0); break; case 5: /* CHV-STATUS */ { /* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the two global passwords followed by the two SigG passwords. For the values, see the function get_chv_status. */ int tmp[4]; /* We use a helper array so that we can control that there is no superfluous application switch. Note that PW2.CH.SIG really has the identifier 0x83 and not 0x82 as one would expect. */ tmp[0] = get_chv_status (app, 0, 0x00); tmp[1] = get_chv_status (app, 0, 0x01); tmp[2] = get_chv_status (app, 1, 0x81); tmp[3] = get_chv_status (app, 1, 0x83); snprintf (buffer, sizeof buffer, "%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]); send_status_info (ctrl, table[idx].name, buffer, strlen (buffer), NULL, 0); } break; default: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; } return err; } static void do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags, int is_sigg) { gpg_error_t err; char ct_buf[100], id_buf[100]; int i; const char *tag; const char *usage; if (is_sigg) tag = "SIGG"; else if (app->app_local->nks_version < 3) tag = "DF01"; else tag = "NKS3"; /* Output information about all useful objects in the NKS application. */ for (i=0; filelist[i].fid; i++) { if (filelist[i].nks_ver > app->app_local->nks_version) continue; if (!!filelist[i].is_sigg != !!is_sigg) continue; if (filelist[i].certtype && !(flags &1)) { size_t len; len = app_help_read_length_of_cert (app->slot, filelist[i].fid, NULL); if (len) { /* FIXME: We should store the length in the application's context so that a following readcert does only need to read that many bytes. */ snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype); snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", tag, filelist[i].fid); send_status_info (ctrl, "CERTINFO", ct_buf, strlen (ct_buf), id_buf, strlen (id_buf), NULL, (size_t)0); } } else if (filelist[i].iskeypair) { char gripstr[40+1]; err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr); if (err) log_error ("can't get keygrip from FID 0x%04X: %s\n", filelist[i].fid, gpg_strerror (err)); else { snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X", tag, filelist[i].fid); if (filelist[i].issignkey && filelist[i].isenckey) usage = "sae"; else if (filelist[i].issignkey) usage = "sa"; else if (filelist[i].isenckey) usage = "e"; else usage = ""; send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, id_buf, strlen (id_buf), usage, strlen (usage), NULL, (size_t)0); } } } } static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; err = switch_application (app, 0); if (err) return err; do_learn_status_core (app, ctrl, flags, 0); err = switch_application (app, 1); if (err) return 0; /* Silently ignore if we can't switch to SigG. */ do_learn_status_core (app, ctrl, flags, 1); 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. */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { int i, 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; int is_sigg = 0; *cert = NULL; *certlen = 0; if (!strncmp (certid, "NKS-NKS3.", 9)) ; else if (!strncmp (certid, "NKS-DF01.", 9)) ; else if (!strncmp (certid, "NKS-SIGG.", 9)) is_sigg = 1; else return gpg_error (GPG_ERR_INV_ID); err = switch_application (app, is_sigg); if (err) return err; certid += 9; 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); for (i=0; filelist[i].fid; i++) if ((filelist[i].certtype || filelist[i].iskeypair) && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); /* If the requested objects is a plain public key, redirect it to the corresponding certificate. The whole system is a bit messy because we sometime use the key directly or let the caller retrieve the key from the certificate. The rationale for that is to support not-yet stored certificates. */ if (filelist[i].iskeypair) fid = filelist[i].iskeypair; /* 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; } /* Handle the READKEY command. On success a canonical encoded S-expression with the public key will get stored at PK and its length at PKLEN; the caller must release that buffer. On error PK and PKLEN are not changed and an error code is returned. As of now this function is only useful for the internal authentication key. Other keys are automagically retrieved via by means of the certificate parsing code in commands.c:cmd_readkey. For internal use PK and PKLEN may be NULL to just check for an existing key. */ static gpg_error_t do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, unsigned char **pk, size_t *pklen) { gpg_error_t err; unsigned char *buffer[2]; size_t buflen[2]; unsigned short path[1] = { 0x4500 }; (void)ctrl; if ((flags & APP_READKEY_FLAG_ADVANCED)) return GPG_ERR_NOT_SUPPORTED; /* We use a generic name to retrieve PK.AUT.IFD-SPK. */ if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) ; else /* Return the error code expected by cmd_readkey. */ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* Access the KEYD file which is always in the master directory. */ err = iso7816_select_path (app_get_slot (app), path, DIM (path), 0); if (err) return err; /* Due to the above select we need to re-select our application. */ app->app_local->need_app_select = 1; /* Get the two records. */ err = iso7816_read_record (app->slot, 5, 1, 0, &buffer[0], &buflen[0]); if (err) return err; if (all_zero_p (buffer[0], buflen[0])) { xfree (buffer[0]); return gpg_error (GPG_ERR_NOT_FOUND); } err = iso7816_read_record (app->slot, 6, 1, 0, &buffer[1], &buflen[1]); if (err) { xfree (buffer[0]); return err; } if (pk && pklen) { *pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0], buffer[1], buflen[1], pklen); if (!*pk) err = gpg_error_from_syserror (); } xfree (buffer[0]); xfree (buffer[1]); return err; } /* Handle the WRITEKEY command for NKS. This function expects a canonical encoded S-expression with the public key in KEYDATA and its length in KEYDATALEN. The only supported KEYID is "$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of FLAGS indicates whether an existing key shall get overwritten. PINCB and PINCB_ARG are the usual arguments for the pinentry callback. */ static gpg_error_t do_writekey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; int force = (flags & 1); const unsigned char *rsa_n = NULL; const unsigned char *rsa_e = NULL; size_t rsa_n_len, rsa_e_len; unsigned int nbits; (void)pincb; (void)pincb_arg; if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3) ; else return gpg_error (GPG_ERR_INV_ID); if (!force && !do_readkey (app, ctrl, keyid, 0, NULL, NULL)) return gpg_error (GPG_ERR_EEXIST); /* Parse the S-expression. */ err = get_rsa_pk_from_canon_sexp (keydata, keydatalen, &rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len); if (err) goto leave; /* Check that the parameters match the requirements. */ nbits = app_help_count_bits (rsa_n, rsa_n_len); if (nbits != 1024) { log_error (_("RSA modulus missing or not of size %d bits\n"), 1024); err = gpg_error (GPG_ERR_BAD_PUBKEY); goto leave; } nbits = app_help_count_bits (rsa_e, rsa_e_len); if (nbits < 2 || nbits > 32) { log_error (_("RSA public exponent missing or larger than %d bits\n"), 32); err = gpg_error (GPG_ERR_BAD_PUBKEY); goto leave; } /* /\* Store them. *\/ */ /* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */ /* if (err) */ /* goto leave; */ /* Send the MSE:Store_Public_Key. */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* mse = xtrymalloc (1000); */ /* mse[0] = 0x80; /\* Algorithm reference. *\/ */ /* mse[1] = 1; */ /* mse[2] = 0x17; */ /* mse[3] = 0x84; /\* Private key reference. *\/ */ /* mse[4] = 1; */ /* mse[5] = 0x77; */ /* mse[6] = 0x7F; /\* Public key parameter. *\/ */ /* mse[7] = 0x49; */ /* mse[8] = 0x81; */ /* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */ /* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */ /* mse[11] = 0x81; */ /* mse[12] = rsa_n_len; */ /* memcpy (mse+12, rsa_n, rsa_n_len); */ /* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */ /* mse[12] = rsa_e_len; */ /* memcpy (mse+12, rsa_e, rsa_e_len); */ /* err = iso7816_manage_security_env (app->slot, 0x81, 0xB6, */ /* mse, sizeof mse); */ leave: return err; } static gpg_error_t basic_pin_checks (const char *pinvalue, int minlen, int maxlen) { if (strlen (pinvalue) < minlen) { log_error ("PIN is too short; minimum length is %d\n", minlen); return gpg_error (GPG_ERR_BAD_PIN); } if (strlen (pinvalue) > maxlen) { log_error ("PIN is too large; maximum length is %d\n", maxlen); return gpg_error (GPG_ERR_BAD_PIN); } return 0; } /* Verify the PIN if required. */ static gpg_error_t verify_pin (app_t app, int pwid, const char *desc, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { pininfo_t pininfo; int rc; if (!desc) desc = "PIN"; memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = 6; pininfo.maxlen = 16; if (!opt.disable_pinpad && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) ) { rc = pincb (pincb_arg, desc, NULL); if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } rc = iso7816_verify_kp (app->slot, pwid, &pininfo); pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else { char *pinvalue; rc = pincb (pincb_arg, desc, &pinvalue); if (rc) { log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); return rc; } rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen); if (rc) { xfree (pinvalue); return rc; } rc = iso7816_verify (app->slot, pwid, pinvalue, strlen (pinvalue)); xfree (pinvalue); } if (rc) { if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS ) log_error (_("the NullPIN has not yet been changed\n")); else log_error ("verify PIN failed\n"); return rc; } 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 }; int rc, i; int is_sigg = 0; int fid; unsigned char kid; unsigned char data[83]; /* Must be large enough for a SHA-1 digest + the largest OID prefix. */ size_t datalen; (void)ctrl; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); switch (indatalen) { case 16: case 20: case 35: case 47: case 51: case 67: case 83: break; default: return gpg_error (GPG_ERR_INV_VALUE); } /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) ; else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) ; else if (!strncmp (keyidstr, "NKS-SIGG.", 9) ) is_sigg = 1; else return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; rc = switch_application (app, is_sigg); if (rc) return rc; if (is_sigg && app->app_local->sigg_is_msig) { log_info ("mass signature cards are not allowed\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) || keyidstr[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (keyidstr); for (i=0; filelist[i].fid; i++) if (filelist[i].iskeypair && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); if (!filelist[i].issignkey) return gpg_error (GPG_ERR_INV_ID); kid = filelist[i].kid; /* Prepare the DER object from INDATA. */ if (app->app_local->nks_version > 2 && (indatalen == 35 || indatalen == 47 || indatalen == 51 || indatalen == 67 || indatalen == 83)) { /* The caller send data matching the length of the ASN.1 encoded hash for SHA-{1,224,256,384,512}. Assume that is okay. */ assert (indatalen <= sizeof data); memcpy (data, indata, indatalen); datalen = indatalen; } else if (indatalen == 35) { /* Alright, the caller was so kind to send us an already prepared DER object. This is for TCOS 2. */ 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); datalen = 35; } else if (indatalen == 20) { if (hashalgo == GCRY_MD_SHA1) memcpy (data, sha1_prefix, 15); else if (hashalgo == GCRY_MD_RMD160) memcpy (data, rmd160_prefix, 15); else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); memcpy (data+15, indata, indatalen); datalen = 35; } else return gpg_error (GPG_ERR_INV_VALUE); /* Send an MSE for PSO:Computer_Signature. */ if (app->app_local->nks_version > 2) { unsigned char mse[6]; mse[0] = 0x80; /* Algorithm reference. */ mse[1] = 1; mse[2] = 2; /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check. */ mse[3] = 0x84; /* Private key reference. */ mse[4] = 1; mse[5] = kid; rc = iso7816_manage_security_env (app->slot, 0x41, 0xB6, mse, sizeof mse); } /* Verify using PW1.CH. */ if (!rc) rc = verify_pin (app, 0, NULL, pincb, pincb_arg); /* Compute the signature. */ if (!rc) rc = iso7816_compute_ds (app->slot, 0, data, datalen, 0, outdata, outdatalen); return rc; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ static gpg_error_t do_decipher (app_t app, ctrl_t ctrl, 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) { int rc, i; int is_sigg = 0; int fid; int kid; (void)ctrl; (void)r_info; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); /* Check that the provided ID is valid. This is not really needed but we do it to enforce correct usage by the caller. */ if (!strncmp (keyidstr, "NKS-NKS3.", 9) ) ; else if (!strncmp (keyidstr, "NKS-DF01.", 9) ) ; else if (!strncmp (keyidstr, "NKS-SIGG.", 9) ) is_sigg = 1; else return gpg_error (GPG_ERR_INV_ID); keyidstr += 9; rc = switch_application (app, is_sigg); if (rc) return rc; if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1) || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) || keyidstr[4]) return gpg_error (GPG_ERR_INV_ID); fid = xtoi_4 (keyidstr); for (i=0; filelist[i].fid; i++) if (filelist[i].iskeypair && filelist[i].fid == fid) break; if (!filelist[i].fid) return gpg_error (GPG_ERR_NOT_FOUND); if (!filelist[i].isenckey) return gpg_error (GPG_ERR_INV_ID); kid = filelist[i].kid; if (app->app_local->nks_version > 2) { unsigned char mse[6]; mse[0] = 0x80; /* Algorithm reference. */ mse[1] = 1; mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */ mse[3] = 0x84; /* Private key reference. */ mse[4] = 1; mse[5] = kid; rc = iso7816_manage_security_env (app->slot, 0x41, 0xB8, mse, sizeof mse); } else { static const unsigned char mse[] = { 0x80, 1, 0x10, /* Select algorithm RSA. */ 0x84, 1, 0x81 /* Select local secret key 1 for decryption. */ }; rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8, mse, sizeof mse); } if (!rc) rc = verify_pin (app, 0, NULL, pincb, pincb_arg); /* Note that we need to use extended length APDUs for TCOS 3 cards. Command chaining does not work. */ if (!rc) rc = iso7816_decipher (app->slot, app->app_local->nks_version > 2? 1:0, indata, indatalen, 0, 0x81, outdata, outdatalen); return rc; } /* Parse a password ID string. Returns NULL on error or a string suitable as passphrase prompt on success. On success stores the reference value for the password at R_PWID and a flag indicating that the SigG application is to be used at R_SIGG. If NEW_MODE is true, the returned description is suitable for a new Password. Supported values for PWIDSTR are: PW1.CH - Global password 1 PW2.CH - Global password 2 PW1.CH.SIG - SigG password 1 PW2.CH.SIG - SigG password 2 */ static const char * parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid) { const char *desc; if (!pwidstr) desc = NULL; else if (!strcmp (pwidstr, "PW1.CH")) { *r_sigg = 0; *r_pwid = 0x00; /* TRANSLATORS: Do not translate the "|*|" prefixes but keep them verbatim at the start of the string. */ desc = (new_mode ? _("|N|Please enter a new PIN for the standard keys.") : _("||Please enter the PIN for the standard keys.")); } else if (!strcmp (pwidstr, "PW2.CH")) { *r_pwid = 0x01; desc = (new_mode ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " "for the standard keys.") : _("|P|Please enter the PIN Unblocking Code (PUK) " "for the standard keys.")); } else if (!strcmp (pwidstr, "PW1.CH.SIG")) { *r_pwid = 0x81; *r_sigg = 1; desc = (new_mode ? _("|N|Please enter a new PIN for the key to create " "qualified signatures.") : _("||Please enter the PIN for the key to create " "qualified signatures.")); } else if (!strcmp (pwidstr, "PW2.CH.SIG")) { *r_pwid = 0x83; /* Yes, that is 83 and not 82. */ *r_sigg = 1; desc = (new_mode ? _("|NP|Please enter a new PIN Unblocking Code (PUK) " "for the key to create qualified signatures.") : _("|P|Please enter the PIN Unblocking Code (PUK) " "for the key to create qualified signatures.")); } else { *r_pwid = 0; /* Only to avoid gcc warning in calling function. */ desc = NULL; /* Error. */ } return desc; } /* Handle the PASSWD command. See parse_pwidstr() for allowed values for CHVNOSTR. */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; char *newpin = NULL; char *oldpin = NULL; size_t newpinlen; size_t oldpinlen; int is_sigg; const char *newdesc; int pwid; pininfo_t pininfo; (void)ctrl; /* The minimum length is enforced by TCOS, the maximum length is just a reasonable value. */ memset (&pininfo, 0, sizeof pininfo); pininfo.minlen = 6; pininfo.maxlen = 16; newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid); if (!newdesc) return gpg_error (GPG_ERR_INV_ID); if ((flags & APP_CHANGE_FLAG_CLEAR)) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = switch_application (app, is_sigg); if (err) return err; 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 = xtrycalloc (1, 6); if (!oldpin) { err = gpg_error_from_syserror (); goto leave; } oldpinlen = 6; } else { const char *desc; int dummy1, dummy2; if ((flags & APP_CHANGE_FLAG_RESET)) { /* Reset mode: Ask for the alternate PIN. */ const char *altpwidstr; if (!strcmp (pwidstr, "PW1.CH")) altpwidstr = "PW2.CH"; else if (!strcmp (pwidstr, "PW2.CH")) altpwidstr = "PW1.CH"; else if (!strcmp (pwidstr, "PW1.CH.SIG")) altpwidstr = "PW2.CH.SIG"; else if (!strcmp (pwidstr, "PW2.CH.SIG")) altpwidstr = "PW1.CH.SIG"; else { err = gpg_error (GPG_ERR_BUG); goto leave; } desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2); } else { /* Regular change mode: Ask for the old PIN. */ desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2); } err = pincb (pincb_arg, desc, &oldpin); if (err) { log_error ("error getting old PIN: %s\n", gpg_strerror (err)); goto leave; } oldpinlen = strlen (oldpin); err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen); if (err) goto leave; } err = pincb (pincb_arg, newdesc, &newpin); if (err) { log_error (_("error getting new PIN: %s\n"), gpg_strerror (err)); goto leave; } newpinlen = strlen (newpin); err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen); if (err) goto leave; if ((flags & APP_CHANGE_FLAG_RESET)) { char *data; size_t datalen = oldpinlen + newpinlen; data = xtrymalloc (datalen); if (!data) { err = gpg_error_from_syserror (); goto leave; } memcpy (data, oldpin, oldpinlen); memcpy (data+oldpinlen, newpin, newpinlen); err = iso7816_reset_retry_counter_with_rc (app->slot, pwid, data, datalen); wipememory (data, datalen); xfree (data); } else err = iso7816_change_reference_data (app->slot, pwid, oldpin, oldpinlen, newpin, newpinlen); leave: xfree (oldpin); xfree (newpin); return err; } /* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */ static gpg_error_t do_check_pin (app_t app, ctrl_t ctrl, const char *pwidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; int pwid; int is_sigg; const char *desc; (void)ctrl; desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid); if (!desc) return gpg_error (GPG_ERR_INV_ID); err = switch_application (app, is_sigg); if (err) return err; return verify_pin (app, pwid, desc, pincb, pincb_arg); } /* Return the version of the NKS application. */ static int get_nks_version (int slot) { unsigned char *result = NULL; size_t resultlen; int type; if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0, NULL, &result, &resultlen)) return 2; /* NKS 2 does not support this command. */ /* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00 vv tt ccccccccccccccccc aa bb cc vvvvvvvvvvv xx vendor (Philips) -+ | | | | | | | chip type -----------+ | | | | | | chip id ----------------+ | | | | | card type (3 - tcos 3) -------------------+ | | | | OS version of card type ---------------------+ | | | OS release of card type ------------------------+ | | OS vendor internal version ------------------------+ | RFU -----------------------------------------------------------+ */ if (resultlen < 16) type = 0; /* Invalid data returned. */ else type = result[8]; xfree (result); return type; } /* If ENABLE_SIGG is true switch to the SigG application if not yet active. If false switch to the NKS application if not yet active. Returns 0 on success. */ static gpg_error_t switch_application (app_t app, int enable_sigg) { gpg_error_t err; if (((app->app_local->sigg_active && enable_sigg) || (!app->app_local->sigg_active && !enable_sigg)) && !app->app_local->need_app_select) return 0; /* Already switched. */ log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS"); if (enable_sigg) err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0); else err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0); if (!err && enable_sigg && app->app_local->nks_version >= 3 && !app->app_local->sigg_msig_checked) { /* Check whether this card is a mass signature card. */ unsigned char *buffer; size_t buflen; const unsigned char *tmpl; size_t tmpllen; app->app_local->sigg_msig_checked = 1; app->app_local->sigg_is_msig = 1; err = iso7816_select_file (app->slot, 0x5349, 0); if (!err) err = iso7816_read_record (app->slot, 1, 1, 0, &buffer, &buflen); if (!err) { tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen); if (tmpl && tmpllen == 12 && !memcmp (tmpl, "\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83", 12)) app->app_local->sigg_is_msig = 0; xfree (buffer); } if (app->app_local->sigg_is_msig) log_info ("This is a mass signature card\n"); } if (!err) { app->app_local->need_app_select = 0; app->app_local->sigg_active = enable_sigg; } else log_error ("app-nks: error switching to %s: %s\n", enable_sigg? "SigG":"NKS", gpg_strerror (err)); return err; } /* Select the NKS application. */ gpg_error_t app_select_nks (app_t app) { int slot = app->slot; int rc; rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0); if (!rc) { app->apptype = APPTYPE_NKS; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } app->app_local->nks_version = get_nks_version (slot); if (opt.verbose) log_info ("Detected NKS version: %d\n", app->app_local->nks_version); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = NULL; app->fnc.writekey = do_writekey; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = NULL; app->fnc.decipher = do_decipher; app->fnc.change_pin = do_change_pin; app->fnc.check_pin = do_check_pin; } leave: if (rc) do_deinit (app); return rc; } diff --git a/sm/certcheck.c b/sm/certcheck.c index 12b3ec927..d6b967c8a 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -1,617 +1,616 @@ /* certcheck.c - check one certificate * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" /* Return the number of bits of the Q parameter from the DSA key KEY. */ static unsigned int get_dsa_qbits (gcry_sexp_t key) { gcry_sexp_t l1, l2; gcry_mpi_t q; unsigned int nbits; l1 = gcry_sexp_find_token (key, "public-key", 0); if (!l1) return 0; /* Does not contain a key object. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); l1 = gcry_sexp_find_token (l2, "q", 1); gcry_sexp_release (l2); if (!l1) return 0; /* Invalid object. */ q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG); gcry_sexp_release (l1); if (!q) return 0; /* Missing value. */ nbits = gcry_mpi_get_nbits (q); gcry_mpi_release (q); return nbits; } static int do_encode_md (gcry_md_hd_t md, int algo, int pkalgo, unsigned int nbits, gcry_sexp_t pkey, gcry_mpi_t *r_val) { int n; size_t nframe; unsigned char *frame; if (pkalgo == GCRY_PK_DSA || pkalgo == GCRY_PK_ECDSA) { unsigned int qbits; if ( pkalgo == GCRY_PK_ECDSA ) qbits = gcry_pk_get_nbits (pkey); else qbits = get_dsa_qbits (pkey); if ( (qbits%8) ) { log_error(_("DSA requires the hash length to be a" " multiple of 8 bits\n")); return gpg_error (GPG_ERR_INTERNAL); } /* Don't allow any Q smaller than 160 bits. We don't want someone to issue signatures from a key with a 16-bit Q or something like that, which would look correct but allow trivial forgeries. Yes, I know this rules out using MD5 with DSA. ;) */ if (qbits < 160) { log_error (_("%s key uses an unsafe (%u bit) hash\n"), gcry_pk_algo_name (pkalgo), qbits); return gpg_error (GPG_ERR_INTERNAL); } /* Check if we're too short. Too long is safe as we'll automatically left-truncate. */ nframe = gcry_md_get_algo_dlen (algo); if (nframe < qbits/8) { log_error (_("a %u bit hash is not valid for a %u bit %s key\n"), (unsigned int)nframe*8, gcry_pk_get_nbits (pkey), gcry_pk_algo_name (pkalgo)); /* FIXME: we need to check the requirements for ECDSA. */ if (nframe < 20 || pkalgo == GCRY_PK_DSA ) return gpg_error (GPG_ERR_INTERNAL); } frame = xtrymalloc (nframe); if (!frame) return out_of_core (); memcpy (frame, gcry_md_read (md, algo), nframe); n = nframe; /* Truncate. */ if (n > qbits/8) n = qbits/8; } else { int i; unsigned char asn[100]; size_t asnlen; size_t len; nframe = (nbits+7) / 8; asnlen = DIM(asn); if (!algo || gcry_md_test_algo (algo)) return gpg_error (GPG_ERR_DIGEST_ALGO); if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) { log_error ("no object identifier for algo %d\n", algo); return gpg_error (GPG_ERR_INTERNAL); } len = gcry_md_get_algo_dlen (algo); if ( len + asnlen + 4 > nframe ) { log_error ("can't encode a %d bit MD into a %d bits frame\n", (int)(len*8), (int)nbits); return gpg_error (GPG_ERR_INTERNAL); } /* We encode the MD in this way: * * 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) * * PAD consists of FF bytes. */ frame = xtrymalloc (nframe); if (!frame) return out_of_core (); n = 0; frame[n++] = 0; frame[n++] = 1; /* block type */ i = nframe - len - asnlen -3 ; assert ( i > 1 ); memset ( frame+n, 0xff, i ); n += i; frame[n++] = 0; memcpy ( frame+n, asn, asnlen ); n += asnlen; memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len; assert ( n == nframe ); } if (DBG_CRYPTO) { int j; log_debug ("encoded hash:"); for (j=0; j < nframe; j++) log_printf (" %02X", frame[j]); log_printf ("\n"); } gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); xfree (frame); return 0; } /* Return the public key algorithm id from the S-expression PKEY. FIXME: libgcrypt should provide such a function. Note that this implementation uses the names as used by libksba. */ static int pk_algo_from_sexp (gcry_sexp_t pkey) { gcry_sexp_t l1, l2; const char *name; size_t n; int algo; l1 = gcry_sexp_find_token (pkey, "public-key", 0); if (!l1) return 0; /* Not found. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); name = gcry_sexp_nth_data (l2, 0, &n); if (!name) algo = 0; /* Not found. */ else if (n==3 && !memcmp (name, "rsa", 3)) algo = GCRY_PK_RSA; else if (n==3 && !memcmp (name, "dsa", 3)) algo = GCRY_PK_DSA; /* Because this function is called only for verification we can assume that ECC actually means ECDSA. */ else if (n==3 && !memcmp (name, "ecc", 3)) algo = GCRY_PK_ECDSA; else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) algo = GCRY_PK_RSA; else algo = 0; gcry_sexp_release (l2); return algo; } /* Return the hash algorithm's algo id from its name given in the * non-null termnated string in (buffer,buflen). Returns 0 on failure * or if the algo is not known. */ static int hash_algo_from_buffer (const void *buffer, size_t buflen) { char *string; int algo; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; algo = gcry_md_map_name (string); if (!algo) log_error ("unknown digest algorithm '%s' used in certificate\n", string); xfree (string); return algo; } /* Return an unsigned integer from the non-null termnated string * (buffer,buflen). Returns 0 on failure. */ static unsigned int uint_from_buffer (const void *buffer, size_t buflen) { char *string; unsigned int val; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; val = strtoul (string, NULL, 10); xfree (string); return val; } /* Extract the hash algorithm and the salt length from the sigval. */ static gpg_error_t extract_pss_params (gcry_sexp_t s_sig, int *r_algo, unsigned int *r_saltlen) { gpg_error_t err; gcry_buffer_t ioarray[2] = { {0}, {0} }; err = gcry_sexp_extract_param (s_sig, "sig-val", "&'hash-algo''salt-length'", ioarray+0, ioarray+1, NULL); if (err) { log_error ("extracting params from PSS failed: %s\n", gpg_strerror (err)); return err; } *r_algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); *r_saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); xfree (ioarray[0].data); xfree (ioarray[1].data); if (*r_saltlen < 20) { log_error ("length of PSS salt too short\n"); - gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } if (!*r_algo) { return gpg_error (GPG_ERR_DIGEST_ALGO); } /* PSS has no hash function firewall like PKCS#1 and thus offers * a path for hash algorithm replacement. To avoid this it makes * sense to restrict the allowed hash algorithms and also allow only * matching salt lengths. According to Peter Gutmann: * "Beware of bugs in the above signature scheme; * I have only proved it secure, not implemented it" * - Apologies to Donald Knuth. * https://www.metzdowd.com/pipermail/cryptography/2019-November/035449.html * * Given the set of supported algorithms currently available in * Libgcrypt and the extra hash checks we have in some compliance * modes, it would be hard to trick gpgsm to verify a forged * signature. However, if eventually someone adds the xor256 hash * algorithm (1.3.6.1.4.1.3029.3.2) to Libgcrypt we would be doomed. */ switch (*r_algo) { case GCRY_MD_SHA1: case GCRY_MD_SHA256: case GCRY_MD_SHA384: case GCRY_MD_SHA512: case GCRY_MD_SHA3_256: case GCRY_MD_SHA3_384: case GCRY_MD_SHA3_512: break; default: log_error ("PSS hash algorithm '%s' rejected\n", gcry_md_algo_name (*r_algo)); return gpg_error (GPG_ERR_DIGEST_ALGO); } if (gcry_md_get_algo_dlen (*r_algo) != *r_saltlen) { log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", gcry_md_algo_name (*r_algo), *r_saltlen); return gpg_error (GPG_ERR_DIGEST_ALGO); } return 0; } /* Check the signature on CERT using the ISSUER-CERT. This function does only test the cryptographic signature and nothing else. It is assumed that the ISSUER_CERT is valid. */ int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) { const char *algoid; gcry_md_hd_t md; int rc, algo; ksba_sexp_t p; size_t n; gcry_sexp_t s_sig, s_data, s_pkey; int use_pss = 0; unsigned int saltlen; algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); if (!algo && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) use_pss = 1; else if (!algo) { log_error ("unknown digest algorithm '%s' used certificate\n", algoid? algoid:"?"); if (algoid && ( !strcmp (algoid, "1.2.840.113549.1.1.2") ||!strcmp (algoid, "1.2.840.113549.2.2"))) log_info (_("(this is the MD2 algorithm)\n")); return gpg_error (GPG_ERR_GENERAL); } /* The the signature from the certificate. */ p = ksba_cert_get_sig_val (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); ksba_free (p); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan ( &s_sig, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } if (DBG_CRYPTO) gcry_log_debugsxp ("sigval", s_sig); if (use_pss) { rc = extract_pss_params (s_sig, &algo, &saltlen); if (rc) { gcry_sexp_release (s_sig); return rc; } } /* Hash the to-be-signed parts of the certificate. */ rc = gcry_md_open (&md, algo, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); return rc; } if (DBG_HASHING) gcry_md_debug (md, "hash.cert"); rc = ksba_cert_hash (cert, 1, HASH_FNC, md); if (rc) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); return rc; } gcry_md_final (md); /* Get the public key from the certificate. */ p = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); gcry_sexp_release (s_sig); return rc; } if (DBG_CRYPTO) gcry_log_debugsxp ("pubkey:", s_pkey); if (use_pss) { rc = gcry_sexp_build (&s_data, NULL, "(data (flags pss)" "(hash %s %b)" "(salt-length %u))", hash_algo_to_string (algo), (int)gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo), saltlen); if (rc) BUG (); } else { /* RSA or DSA: Prepare the hash for verification. */ gcry_mpi_t frame; rc = do_encode_md (md, algo, pk_algo_from_sexp (s_pkey), gcry_pk_get_nbits (s_pkey), s_pkey, &frame); if (rc) { gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_pkey); return rc; } if ( gcry_sexp_build (&s_data, NULL, "%m", frame) ) BUG (); gcry_mpi_release (frame); } if (DBG_CRYPTO) gcry_log_debugsxp ("data:", s_data); /* Verify. */ rc = gcry_pk_verify (s_sig, s_data, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); return rc; } int gpgsm_check_cms_signature (ksba_cert_t cert, gcry_sexp_t s_sig, gcry_md_hd_t md, int mdalgo, unsigned int pkalgoflags, int *r_pkalgo) { int rc; ksba_sexp_t p; gcry_sexp_t s_hash, s_pkey; size_t n; int pkalgo; int use_pss; unsigned int saltlen = 0; if (r_pkalgo) *r_pkalgo = 0; /* Check whether rsaPSS is needed. This information is indicated in * the SIG-VAL and already provided to us by the caller so that we * do not need to parse this out. */ use_pss = !!(pkalgoflags & PK_ALGO_FLAG_RSAPSS); if (use_pss) { int algo; rc = extract_pss_params (s_sig, &algo, &saltlen); if (rc) { gcry_sexp_release (s_sig); return rc; } if (algo != mdalgo) { log_error ("PSS hash algo mismatch (%d/%d)\n", mdalgo, algo); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } } p = ksba_cert_get_public_key (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); ksba_free (p); return gpg_error (GPG_ERR_BUG); } if (DBG_CRYPTO) log_printhex (p, n, "public key: "); rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n); ksba_free (p); if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } pkalgo = pk_algo_from_sexp (s_pkey); if (r_pkalgo) *r_pkalgo = pkalgo; if (use_pss) { rc = gcry_sexp_build (&s_hash, NULL, "(data (flags pss)" "(hash %s %b)" "(salt-length %u))", hash_algo_to_string (mdalgo), (int)gcry_md_get_algo_dlen (mdalgo), gcry_md_read (md, mdalgo), saltlen); if (rc) BUG (); } else { /* RSA or DSA: Prepare the hash for verification. */ gcry_mpi_t frame; rc = do_encode_md (md, mdalgo, pkalgo, gcry_pk_get_nbits (s_pkey), s_pkey, &frame); if (rc) { gcry_sexp_release (s_pkey); return rc; } /* put hash into the S-Exp s_hash */ if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) BUG (); gcry_mpi_release (frame); } rc = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return rc; } int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, unsigned char **r_sigval) { int rc; char *grip, *desc; size_t siglen; grip = gpgsm_get_keygrip_hexstring (cert); if (!grip) return gpg_error (GPG_ERR_BAD_CERT); desc = gpgsm_format_keydesc (cert); rc = gpgsm_agent_pksign (ctrl, grip, desc, gcry_md_read(md, mdalgo), gcry_md_get_algo_dlen (mdalgo), mdalgo, r_sigval, &siglen); xfree (desc); xfree (grip); return rc; } diff --git a/sm/encrypt.c b/sm/encrypt.c index 331bfa8ba..37cfe9263 100644 --- a/sm/encrypt.c +++ b/sm/encrypt.c @@ -1,579 +1,580 @@ /* encrypt.c - Encrypt a message * Copyright (C) 2001, 2003, 2004, 2007, 2008, * 2010 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 . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../common/i18n.h" #include "../common/compliance.h" struct dek_s { const char *algoid; int algo; gcry_cipher_hd_t chd; char key[32]; int keylen; char iv[32]; int ivlen; }; typedef struct dek_s *DEK; /* Callback parameters for the encryption. */ struct encrypt_cb_parm_s { estream_t fp; DEK dek; int eof_seen; int ready; int readerror; int bufsize; unsigned char *buffer; int buflen; }; /* Initialize the data encryption key (session key). */ static int init_dek (DEK dek) { int rc=0, mode, i; dek->algo = gcry_cipher_map_name (dek->algoid); mode = gcry_cipher_mode_from_oid (dek->algoid); if (!dek->algo || !mode) { log_error ("unsupported algorithm '%s'\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } /* Extra check for algorithms we consider to be too weak for encryption, although we support them for decryption. Note that there is another check below discriminating on the key length. */ switch (dek->algo) { case GCRY_CIPHER_DES: case GCRY_CIPHER_RFC2268_40: log_error ("cipher algorithm '%s' not allowed: too weak\n", gnupg_cipher_algo_name (dek->algo)); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); default: break; } dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); if (!dek->keylen || dek->keylen > sizeof (dek->key)) return gpg_error (GPG_ERR_BUG); dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) return gpg_error (GPG_ERR_BUG); /* Make sure we don't use weak keys. */ if (dek->keylen < 100/8) { log_error ("key length of '%s' too small\n", dek->algoid); return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); if (rc) { log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); return rc; } for (i=0; i < 8; i++) { gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) break; log_info(_("weak key created - retrying\n") ); } if (rc) { log_error ("failed to set the key: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } gcry_create_nonce (dek->iv, dek->ivlen); rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); if (rc) { log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); gcry_cipher_close (dek->chd); dek->chd = NULL; return rc; } return 0; } static int encode_session_key (DEK dek, gcry_sexp_t * r_data) { gcry_sexp_t data; char *p; int rc; p = xtrymalloc (64 + 2 * dek->keylen); if (!p) return gpg_error_from_syserror (); strcpy (p, "(data\n (flags pkcs1)\n (value #"); bin2hex (dek->key, dek->keylen, p + strlen (p)); strcat (p, "#))\n"); rc = gcry_sexp_sscan (&data, NULL, p, strlen (p)); xfree (p); *r_data = data; return rc; } /* Encrypt the DEK under the key contained in CERT and return it as a canonical S-Exp in encval. */ static int encrypt_dek (const DEK dek, ksba_cert_t cert, unsigned char **encval) { gcry_sexp_t s_ciph, s_data, s_pkey; int rc; ksba_sexp_t buf; size_t len; *encval = NULL; /* get the key from the cert */ buf = ksba_cert_get_public_key (cert); if (!buf) { log_error ("no public key for recipient\n"); return gpg_error (GPG_ERR_NO_PUBKEY); } len = gcry_sexp_canon_len (buf, 0, NULL, NULL); if (!len) { log_error ("libksba did not return a proper S-Exp\n"); return gpg_error (GPG_ERR_BUG); } rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)buf, len); xfree (buf); buf = NULL; if (rc) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); return rc; } /* Put the encoded cleartext into a simple list. */ s_data = NULL; /* (avoid compiler warning) */ rc = encode_session_key (dek, &s_data); if (rc) { + gcry_sexp_release (s_pkey); log_error ("encode_session_key failed: %s\n", gpg_strerror (rc)); return rc; } /* pass it to libgcrypt */ rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); gcry_sexp_release (s_data); gcry_sexp_release (s_pkey); /* Reformat it. */ if (!rc) { rc = make_canon_sexp (s_ciph, encval, NULL); gcry_sexp_release (s_ciph); } return rc; } /* do the actual encryption */ static int encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) { struct encrypt_cb_parm_s *parm = cb_value; int blklen = parm->dek->ivlen; unsigned char *p; size_t n; *nread = 0; if (!buffer) return -1; /* not supported */ if (parm->ready) return -1; if (count < blklen) BUG (); if (!parm->eof_seen) { /* fillup the buffer */ p = parm->buffer; for (n=parm->buflen; n < parm->bufsize; n++) { int c = es_getc (parm->fp); if (c == EOF) { if (es_ferror (parm->fp)) { parm->readerror = errno; return -1; } parm->eof_seen = 1; break; } p[n] = c; } parm->buflen = n; } n = parm->buflen < count? parm->buflen : count; n = n/blklen * blklen; if (n) { /* encrypt the stuff */ gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; /* Who cares about cycles, take the easy way and shift the buffer */ parm->buflen -= n; memmove (parm->buffer, parm->buffer+n, parm->buflen); } else if (parm->eof_seen) { /* no complete block but eof: add padding */ /* fixme: we should try to do this also in the above code path */ int i, npad = blklen - (parm->buflen % blklen); p = parm->buffer; for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) p[n] = npad; gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); *nread = n; parm->ready = 1; } return 0; } /* Perform an encrypt operation. Encrypt the data received on DATA-FD and write it to OUT_FP. The recipients are take from the certificate given in recplist; if this is NULL it will be encrypted for a default recipient */ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int data_fd, estream_t out_fp) { int rc = 0; gnupg_ksba_io_t b64writer = NULL; gpg_error_t err; ksba_writer_t writer; ksba_reader_t reader = NULL; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; struct encrypt_cb_parm_s encparm; DEK dek = NULL; int recpno; estream_t data_fp = NULL; certlist_t cl; int count; int compliant; memset (&encparm, 0, sizeof encparm); audit_set_type (ctrl->audit, AUDIT_TYPE_ENCRYPT); /* Check that the certificate list is not empty and that at least one certificate is not flagged as encrypt_to; i.e. is a real recipient. */ for (cl = recplist; cl; cl = cl->next) if (!cl->is_encrypt_to) break; if (!cl) { log_error(_("no valid recipients given\n")); gpgsm_status (ctrl, STATUS_NO_RECP, "0"); audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, 0); rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } for (count = 0, cl = recplist; cl; cl = cl->next) count++; audit_log_i (ctrl->audit, AUDIT_GOT_RECIPIENTS, count); kh = keydb_new (); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } /* Fixme: We should use the unlocked version of the es functions. */ data_fp = es_fdopen_nc (data_fd, "rb"); if (!data_fp) { rc = gpg_error_from_syserror (); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } err = ksba_reader_new (&reader); if (err) rc = err; if (!rc) rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); if (rc) goto leave; encparm.fp = data_fp; ctrl->pem_name = "ENCRYPTED MESSAGE"; 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)); goto leave; } err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, reader, writer); if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } audit_log (ctrl->audit, AUDIT_GOT_DATA); /* We are going to create enveloped data with uninterpreted data as inner content */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* Check compliance. */ if (!gnupg_cipher_is_allowed (opt.compliance, 1, gcry_cipher_map_name (opt.def_cipher_algoid), gcry_cipher_mode_from_oid (opt.def_cipher_algoid))) { log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), opt.def_cipher_algoid, gnupg_compliance_option_string (opt.compliance)); rc = gpg_error (GPG_ERR_CIPHER_ALGO); goto leave; } if (!gnupg_rng_is_compliant (opt.compliance)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), "RNG", gnupg_compliance_option_string (opt.compliance)); gpgsm_status_with_error (ctrl, STATUS_ERROR, "random-compliance", rc); goto leave; } /* Create a session key */ dek = xtrycalloc_secure (1, sizeof *dek); if (!dek) rc = out_of_core (); else { dek->algoid = opt.def_cipher_algoid; rc = init_dek (dek); } if (rc) { log_error ("failed to create the session key: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); if (err) { log_error ("ksba_cms_set_content_enc_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } encparm.dek = dek; /* Use a ~8k (AES) or ~4k (3DES) buffer */ encparm.bufsize = 500 * dek->ivlen; encparm.buffer = xtrymalloc (encparm.bufsize); if (!encparm.buffer) { rc = out_of_core (); goto leave; } audit_log_s (ctrl->audit, AUDIT_SESSION_KEY, dek->algoid); compliant = gnupg_cipher_is_compliant (CO_DE_VS, dek->algo, GCRY_CIPHER_MODE_CBC); /* Gather certificates of recipients, encrypt the session key for each and store them in the CMS object */ for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) { unsigned char *encval; unsigned int nbits; int pk_algo; /* Check compliance. */ pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits); if (!gnupg_pk_is_compliant (opt.compliance, pk_algo, 0, NULL, nbits, NULL)) { char kidstr[10+1]; snprintf (kidstr, sizeof kidstr, "0x%08lX", gpgsm_get_short_fingerprint (cl->cert, NULL)); log_info (_("WARNING: key %s is not suitable for encryption" " in %s mode\n"), kidstr, gnupg_compliance_option_string (opt.compliance)); } /* Fixme: When adding ECC we need to provide the curvename and * the key to gnupg_pk_is_compliant. */ if (compliant && !gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0, NULL, nbits, NULL)) compliant = 0; rc = encrypt_dek (dek, cl->cert, &encval); if (rc) { audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, rc); log_error ("encryption failed for recipient no. %d: %s\n", recpno, gpg_strerror (rc)); goto leave; } err = ksba_cms_add_recipient (cms, cl->cert); if (err) { audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); log_error ("ksba_cms_add_recipient failed: %s\n", gpg_strerror (err)); rc = err; xfree (encval); goto leave; } err = ksba_cms_set_enc_val (cms, recpno, encval); xfree (encval); audit_log_cert (ctrl->audit, AUDIT_ENCRYPTED_TO, cl->cert, err); if (err) { log_error ("ksba_cms_set_enc_val failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } if (compliant && gnupg_gcrypt_is_compliant (CO_DE_VS)) gpgsm_status (ctrl, STATUS_ENCRYPTION_COMPLIANCE_MODE, gnupg_status_compliance_flag (CO_DE_VS)); /* Main control loop for encryption. */ recpno = 0; do { err = ksba_cms_build (cms, &stopreason); if (err) { log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } while (stopreason != KSBA_SR_READY); if (encparm.readerror) { log_error ("error reading input: %s\n", strerror (encparm.readerror)); rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); goto leave; } rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } audit_log (ctrl->audit, AUDIT_ENCRYPTION_DONE); if (!opt.quiet) log_info ("encrypted data created\n"); leave: ksba_cms_release (cms); gnupg_ksba_destroy_writer (b64writer); ksba_reader_release (reader); keydb_release (kh); xfree (dek); es_fclose (data_fp); xfree (encparm.buffer); return rc; } diff --git a/sm/server.c b/sm/server.c index 77ec07fc0..5341d315a 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1493 +1,1508 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, * 2010 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 . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include "../common/sysutils.h" #include "../common/server-help.h" #include "../common/asshelp.h" #include "../common/shareddefs.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; int list_to_output; /* Write keylistings to the output fd. */ int enable_audit_log; /* Use an audit log. */ certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ int allow_pinentry_notify; /* Set if pinentry notifications should be passed back to the client. */ int no_encrypt_to; /* Local version of option. */ }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; static int command_has_option (const char *cmd, const char *cmdopt); /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { gpg_err_set_errno (EIO); return -1; } return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } static void close_message_fd (ctrl_t ctrl) { if (ctrl->server_local->message_fd != -1) { #ifdef HAVE_W32CE_SYSTEM #warning Is this correct for W32/W32CE? #endif close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } /* Start a new audit session if this has been enabled. */ static gpg_error_t start_audit_session (ctrl_t ctrl) { audit_release (ctrl->audit); ctrl->audit = NULL; if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) return gpg_error_from_syserror (); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) err = gpg_error (GPG_ERR_ASS_PARAMETER); else ctrl->include_certs = i; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "list-to-output")) { int i = *value? atoi (value) : 0; ctrl->server_local->list_to_output = i; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else if (!strcmp (key, "with-secret")) { int i = *value? atoi (value) : 0; ctrl->with_secret = i; } else if (!strcmp (key, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 2 ) ctrl->validation_model = i; else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "with-key-data")) { opt.with_key_data = 1; } else if (!strcmp (key, "enable-audit-log")) { int i = *value? atoi (value) : 0; ctrl->server_local->enable_audit_log = i; } else if (!strcmp (key, "allow-pinentry-notify")) { ctrl->server_local->allow_pinentry_notify = 1; } else if (!strcmp (key, "with-ephemeral-keys")) { int i = *value? atoi (value) : 0; ctrl->with_ephemeral_keys = i; } else if (!strcmp (key, "no-encrypt-to")) { ctrl->server_local->no_encrypt_to = 1; } else if (!strcmp (key, "offline")) { /* We ignore this option if gpgsm has been started with --disable-dirmngr (which also sets offline). */ if (!opt.disable_dirmngr) { int i = *value? !!atoi (value) : 1; ctrl->offline = i; } } else if (!strcmp (key, "request-origin")) { if (!opt.request_origin) { int i = parse_request_origin (value); if (i == -1) err = gpg_error (GPG_ERR_INV_VALUE); else opt.request_origin = i; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static gpg_error_t input_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; return 0; } static gpg_error_t output_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ return 0; } static const char hlp_recipient[] = "RECIPIENT \n" "\n" "Set the recipient for the encryption. USERID shall be the\n" "internal representation of the key; the server may accept any other\n" "way of specification [we will support this]. If this is a valid and\n" "trusted recipient the server does respond with OK, otherwise the\n" "return is an ERR with the reason why the recipient can't be used,\n" "the encryption will then not be done for this recipient. If the\n" "policy is not to encrypt at all if not all recipients are valid, the\n" "client has to take care of this. All RECIPIENT commands are\n" "cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; if (!ctrl->audit) rc = start_audit_session (ctrl); else rc = 0; if (!rc) rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_signer[] = "SIGNER \n" "\n" "Set the signer's keys for the signature creation. USERID should\n" "be the internal representation of the key; the server may accept any\n" "other way of specification [we will support this]. If this is a\n" "valid and usable signing key the server does respond with OK,\n" "otherwise it returns an ERR with the reason why the key can't be\n" "used, the signing will then not be done for this key. If the policy\n" "is not to sign at all if not all signer keys are valid, the client\n" "has to take care of this. All SIGNER commands are cumulative until\n" "a RESET but they are *not* reset by an SIGN command because it can\n" "be expected that set of signers are used for more than one sign\n" "operation."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), line, NULL); /* For compatibility reasons we also issue the old code after the new one. */ gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_encrypt[] = "ENCRYPT \n" "\n" "Do the actual encryption process. Takes the plaintext from the INPUT\n" "command, writes to the ciphertext to the file descriptor set with\n" "the OUTPUT command, take the recipients form all the recipients set\n" "so far. If this command fails the clients should try to delete all\n" "output currently done or otherwise mark it as invalid. GPGSM does\n" "ensure that there won't be any security problem with leftover data\n" "on the output in this case.\n" "\n" "This command should in general not fail, as all necessary checks\n" "have been done while setting the recipients. The input and output\n" "pipes are closed."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = ctrl->audit? 0 : start_audit_session (ctrl); if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); es_fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_decrypt[] = "DECRYPT\n" "\n" "This performs the decrypt operation after doing some check on the\n" "internal state. (e.g. that only needed data has been set). Because\n" "it utilizes the GPG-Agent for the session key decryption, there is\n" "no need to ask the client for a protecting passphrase - GPG-Agent\n" "does take care of this by requesting this from the user."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); es_fclose (out_fp); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_verify[] = "VERIFY\n" "\n" "This does a verify operation on the message send to the input FD.\n" "The result is written out using status lines. If an output FD was\n" "given, the signed text will be written to that.\n" "\n" "If the signature is a detached one, the server will inquire about\n" "the signed material and the client must provide it."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp = NULL; (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); if (out_fd != -1) { out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); es_fclose (out_fp); /* Close and reset the fd. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_sign[] = "SIGN [--detached]\n" "\n" "Sign the data set with the INPUT command and write it to the sink\n" "set by OUTPUT. With \"--detached\", a detached signature is\n" "created (surprise)."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int detached; int rc; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); detached = has_option (line, "--detached"); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); es_fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_import[] = "IMPORT [--re-import]\n" "\n" "Import the certificates read form the input-fd, return status\n" "message for each imported one. The import checks the validity of\n" "the certificate but not of the entire chain. It is possible to\n" "import expired certificates.\n" "\n" "With the option --re-import the input data is expected to a be a LF\n" "separated list of fingerprints. The command will re-import these\n" "certificates, meaning that they are made permanent by removing\n" "their ephemeral flag."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int reimport = has_option (line, "--re-import"); (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_export[] = "EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] \n" "\n" "Export the certificates selected by PATTERN. With --data the output\n" "is returned using Assuan D lines; the default is to use the sink given\n" "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" "the output using the PEM respective a plain base-64 format; the default\n" "is a binary format which is only suitable for a single certificate.\n" "With --secret the secret key is exported using the PKCS#8 format,\n" "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int use_data; int opt_secret; int opt_raw = 0; int opt_pkcs12 = 0; use_data = has_option (line, "--data"); if (use_data) { /* We need to override any possible setting done by an OUTPUT command. */ ctrl->create_pem = has_option (line, "--armor"); ctrl->create_base64 = has_option (line, "--base64"); } opt_secret = has_option (line, "--secret"); if (opt_secret) { opt_raw = has_option (line, "--raw"); opt_pkcs12 = has_option (line, "--pkcs12"); } line = skip_options (line); /* Break the line down into an strlist_t. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_secret) { - if (!list || !*list->d) + if (!list) return set_error (GPG_ERR_NO_DATA, "No key given"); + if (!*list->d) + { + free_strlist (list); + return set_error (GPG_ERR_NO_DATA, "No key given"); + } if (list->next) return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); } if (use_data) { estream_t stream; stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!stream) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, stream, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, stream); es_fclose (stream); } else { int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp; if (fd == -1) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } out_fp = es_fdopen_nc (fd, "w"); if (!out_fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, out_fp, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, out_fp); es_fclose (out_fp); } free_strlist (list); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static const char hlp_delkeys[] = "DELKEYS \n" "\n" "Delete the certificates specified by PATTERNS. Each pattern shall be\n" "a percent-plus escaped certificate specification. Usually a\n" "fingerprint will be used for this."; static gpg_error_t cmd_delkeys (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int rc; /* break the line down into an strlist_t */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"INPUT\" and \"MESSAGE\" commands."; static const char hlp_input[] = "INPUT FD[=]\n" "\n" "Set the file descriptor to read the input data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"MESSAGE\" and \"OUTPUT\" commands."; static const char hlp_message[] = "MESSAGE FD[=]\n" "\n" "Set the file descriptor to read the message for a detached\n" "signatures to N. If N is not given and the operating system\n" "supports file descriptor passing, the file descriptor currently in\n" "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { int rc; gnupg_fd_t sysfd; int fd; ctrl_t ctrl = assuan_get_pointer (ctx); rc = assuan_command_parse_fd (ctx, line, &sysfd); if (rc) return rc; #ifdef HAVE_W32CE_SYSTEM sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0); if (sysfd == INVALID_HANDLE_VALUE) return set_error (gpg_err_code_from_syserror (), "rvid conversion failed"); #endif fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->server_local->message_fd = fd; return 0; } static const char hlp_listkeys[] = "LISTKEYS []\n" "LISTSECRETKEYS []\n" "DUMPKEYS []\n" "DUMPSECRETKEYS []\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" "pattern shall be a percent-plus escaped certificate specification.\n" "The \"SECRET\" versions of the command filter the output to include\n" "only certificates where the secret key is available or a corresponding\n" "smartcard has been registered. The \"DUMP\" versions of the command\n" "are only useful for debugging. The output format is a percent escaped\n" "colon delimited listing as described in the manual.\n" "\n" "These \"OPTION\" command keys effect the output::\n" "\n" " \"list-mode\" set to 0: List only local certificates (default).\n" " 1: Ditto.\n" " 2: List only external certificates.\n" " 3: List local and external certificates.\n" "\n" " \"with-validation\" set to true: Validate each certificate.\n" "\n" " \"with-ephemeral-key\" set to true: Always include ephemeral\n" " certificates.\n" "\n" " \"list-to-output\" set to true: Write output to the file descriptor\n" " given by the last \"OUTPUT\" command."; static int do_listkeys (assuan_context_t ctx, char *line, int mode) { ctrl_t ctrl = assuan_get_pointer (ctx); estream_t fp; char *p; strlist_t list, sl; unsigned int listmode; gpg_error_t err; /* Break the line down into an strlist. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (ctrl->server_local->list_to_output) { int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if ( outfd == -1 ) - return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + { + free_strlist (list); + return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); + } fp = es_fdopen_nc (outfd, "w"); if (!fp) - return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed"); + { + free_strlist (list); + return set_error (gpg_err_code_from_syserror (), + "es_fdopen() failed"); + } } else { fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) - return set_error (GPG_ERR_ASS_GENERAL, - "error setting up a data stream"); + { + free_strlist (list); + return set_error (GPG_ERR_ASS_GENERAL, + "error setting up a data stream"); + } } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); es_fclose (fp); if (ctrl->server_local->list_to_output) assuan_close_output_fd (ctx); return err; } static gpg_error_t cmd_listkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 3); } static gpg_error_t cmd_dumpkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 259); } static gpg_error_t cmd_listsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 2); } static gpg_error_t cmd_dumpsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 258); } static const char hlp_genkey[] = "GENKEY\n" "\n" "Read the parameters in native format from the input fd and write a\n" "certificate request to the output."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t in_stream, out_stream; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); in_stream = es_fdopen_nc (inp_fd, "r"); if (!in_stream) return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { es_fclose (in_stream); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = gpgsm_genkey (ctrl, in_stream, out_stream); es_fclose (out_stream); es_fclose (in_stream); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_getauditlog[] = "GETAUDITLOG [--data] [--html]\n" "\n" "If --data is used, the output is send using D-lines and not to the\n" "file descriptor given by an OUTPUT command.\n" "\n" "If --html is used the output is formatted as an XHTML block. This is\n" "designed to be incorporated into a HTML document."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int out_fd; estream_t out_stream; int opt_data, opt_html; int rc; opt_data = has_option (line, "--data"); opt_html = has_option (line, "--html"); /* Not needed: line = skip_options (line); */ if (!ctrl->audit) return gpg_error (GPG_ERR_NO_DATA); if (opt_data) { out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!out_stream) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } else { out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); } } audit_print_result (ctrl->audit, out_stream, opt_html); rc = 0; es_fclose (out_stream); /* Close and reset the fd. */ if (!opt_data) assuan_close_output_fd (ctx); return rc; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" " offline - Returns OK if the connection is in offline mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "agent-check")) { rc = gpgsm_agent_send_nop (ctrl); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_FALSE); } } } } else if (!strcmp (line, "offline")) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_FALSE); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_passwd[] = "PASSWD \n" "\n" "Change the passphrase of the secret key for USERID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; char *grip = NULL; line = skip_options (line); err = gpgsm_find_cert (ctrl, line, NULL, &cert, 0); if (err) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) err = gpg_error (GPG_ERR_INTERNAL); else { char *desc = gpgsm_format_keydesc (cert); err = gpgsm_agent_passwd (ctrl, grip, desc); xfree (desc); } xfree (grip); ksba_cert_release (cert); return err; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "IMPORT")) { if (!strcmp (cmdopt, "re-import")) return 1; } return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "RECIPIENT", cmd_recipient, hlp_recipient }, { "SIGNER", cmd_signer, hlp_signer }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "SIGN", cmd_sign, hlp_sign }, { "IMPORT", cmd_import, hlp_import }, { "EXPORT", cmd_export, hlp_export }, { "INPUT", NULL, hlp_input }, { "OUTPUT", NULL, hlp_output }, { "MESSAGE", cmd_message, hlp_message }, { "LISTKEYS", cmd_listkeys, hlp_listkeys }, { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, { "GENKEY", cmd_genkey, hlp_genkey }, { "DELKEYS", cmd_delkeys, hlp_delkeys }, { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; assuan_fd_t filedes[2]; assuan_context_t ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FILEDES in this case. */ #ifdef HAVE_W32CE_SYSTEM #define SERVER_STDIN es_fileno(es_stdin) #define SERVER_STDOUT es_fileno(es_stdout) #else #define SERVER_STDIN 0 #define SERVER_STDOUT 1 #endif filedes[0] = assuan_fdopen (SERVER_STDIN); filedes[1] = assuan_fdopen (SERVER_STDOUT); rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = assuan_init_pipe_server (ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp; /* Fixme: Use the really used socket name. */ if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "DirmngrInfo: %s\n" "%s", gnupg_homedir (), opt.config_filename, dirmngr_socket_name (), hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; xfree (ctrl.server_local); audit_release (ctrl.audit); ctrl.audit = NULL; assuan_release (ctx); } gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); fflush (statusfp); } else { err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx, get_status_string (no), arg_ptr); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; sprintf (buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err) { char buf[30]; snprintf (buf, sizeof buf, "%u", err); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } /* Helper to notify the client about Pinentry events. Because that might disturb some older clients, this is only done when enabled via an option. Returns an gpg error code. */ gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); }