diff --git a/agent/ChangeLog b/agent/ChangeLog index 91dd3acf3..7ea18d943 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,29 @@ +2010-06-15 Werner Koch + + * command.c (cmd_keywrap_key, cmd_import_key): New. + + * genkey.c (agent_genkey, agent_protect_and_store): Factor common + code out to... + (agent_ask_new_passphrase): .. new. + + * findkey.c (agent_write_private_key): Return GPG_ERR_EEXIST + instead of GPG_ERR_GENERAL. + +2010-06-14 Werner Koch + + * protect-tool.c: Remove commands --p12-import and --p12-export. + * minip12.c, minip12.h: Move to ../sm. + * Makefile.am (gpg_protect_tool_SOURCES): Remove them. + * preset-passphrase.c: Remove unneeded minip12.h. + + * command.c (cmd_keywrap_key): New. + + * command.c (leave_cmd): New. + (cmd_istrusted, cmd_listtrusted, cmd_marktrusted, cmd_pksign) + (cmd_pkdecrypt, cmd_genkey, cmd_readkey, cmd_keyinfo) + (cmd_get_passphrase, cmd_get_confirmation, cmd_learn) + (cmd_passwd, cmd_preset_passphrase, cmd_getval, cmd_putval): Use it. + 2010-05-12 Werner Koch * preset-passphrase.c (forget_passphrase): Actually implement diff --git a/agent/Makefile.am b/agent/Makefile.am index 9258fc86d..e22153681 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -79,8 +79,7 @@ gpg_agent_DEPENDENCIES = $(gpg_agent_res_deps) gpg_protect_tool_SOURCES = \ protect-tool.c \ - protect.c \ - minip12.c minip12.h + protect.c gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) gpg_protect_tool_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) \ diff --git a/agent/agent.h b/agent/agent.h index 09519d48b..b39f2325c 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -284,6 +284,8 @@ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, /*-- genkey.c --*/ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int silent); +gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, + char **r_passphrase); int agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparmlen, membuf_t *outbuf); int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); diff --git a/agent/command.c b/agent/command.c index 082e730bd..9bd5ce5c3 100644 --- a/agent/command.c +++ b/agent/command.c @@ -38,10 +38,14 @@ #include #include "i18n.h" -/* maximum allowed size of the inquired ciphertext */ +/* Maximum allowed size of the inquired ciphertext. */ #define MAXLEN_CIPHERTEXT 4096 -/* maximum allowed size of the key parameters */ +/* Maximum allowed size of the key parameters. */ #define MAXLEN_KEYPARAM 1024 +/* Maximum allowed size of key data as used in inquiries (bytes). */ +#define MAXLEN_KEYDATA 4096 +/* The size of the import/export KEK key (in bytes). */ +#define KEYWRAP_KEYSIZE (128/8) #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) @@ -63,6 +67,8 @@ struct server_local_s the end of this session. */ int allow_pinentry_notify; /* Set if pinentry notifications should be done. */ + void *import_key; /* Malloced KEK for the import_key command. */ + void *export_key; /* Malloced KEK for the export_key command. */ }; @@ -340,6 +346,26 @@ agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid) } +/* Helper to print a message while leaving a command. */ +static gpg_error_t +leave_cmd (assuan_context_t ctx, gpg_error_t err) +{ + if (err) + { + const char *name = assuan_get_command_name (ctx); + if (!name) + name = "?"; + if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + log_error ("command '%s' failed: %s\n", name, + gpg_strerror (err)); + else + log_error ("command '%s' failed: %s <%s>\n", name, + gpg_strerror (err), gpg_strsource (err)); + } + return err; +} + + static const char hlp_geteventcounter[] = "GETEVENTCOUNTER\n" @@ -432,10 +458,7 @@ cmd_istrusted (assuan_context_t ctx, char *line) else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF ) return gpg_error (GPG_ERR_NOT_TRUSTED); else - { - log_error ("command is_trusted failed: %s\n", gpg_strerror (rc)); - return rc; - } + return leave_cmd (ctx, rc); } @@ -451,9 +474,7 @@ cmd_listtrusted (assuan_context_t ctx, char *line) (void)line; rc = agent_listtrusted (ctx); - if (rc) - log_error ("command listtrusted failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -494,9 +515,7 @@ cmd_marktrusted (assuan_context_t ctx, char *line) p++; rc = agent_marktrusted (ctrl, p, fpr, flag); - if (rc) - log_error ("command marktrusted failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -695,11 +714,9 @@ cmd_pksign (assuan_context_t ctx, char *line) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); - if (rc) - log_error ("command pksign failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; - return rc; + return leave_cmd (ctx, rc); } @@ -734,11 +751,9 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); - if (rc) - log_error ("command pkdecrypt failed: %s\n", gpg_strerror (rc)); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; - return rc; + return leave_cmd (ctx, rc); } @@ -780,9 +795,7 @@ cmd_genkey (assuan_context_t ctx, char *line) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); - if (rc) - log_error ("command genkey failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -825,9 +838,7 @@ cmd_readkey (assuan_context_t ctx, char *line) gcry_sexp_release (s_pkey); } - if (rc) - log_error ("command readkey failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -967,7 +978,7 @@ cmd_keyinfo (assuan_context_t ctx, char *line) if (dir) closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) - log_error ("command keyinfo failed: %s\n", gpg_strerror (err)); + leave_cmd (ctx, err); return err; } @@ -1167,9 +1178,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) } } - if (rc) - log_error ("command get_passphrase failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1240,9 +1249,7 @@ cmd_get_confirmation (assuan_context_t ctx, char *line) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); - if (rc) - log_error ("command get_confirmation failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1259,9 +1266,7 @@ cmd_learn (assuan_context_t ctx, char *line) int rc; rc = agent_handle_learn (ctrl, has_option (line, "--send")? ctx : NULL); - if (rc) - log_error ("command learn failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1304,9 +1309,7 @@ cmd_passwd (assuan_context_t ctx, char *line) leave: gcry_sexp_release (s_skey); xfree (shadow_info); - if (rc) - log_error ("command passwd failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1371,10 +1374,7 @@ cmd_preset_passphrase (assuan_context_t ctx, char *line) if (!rc) rc = agent_put_cache (grip_clear, CACHE_MODE_ANY, passphrase, ttl); - if (rc) - log_error ("command preset_passphrase failed: %s\n", gpg_strerror (rc)); - - return rc; + return leave_cmd (ctx, rc); } @@ -1396,6 +1396,186 @@ cmd_scd (assuan_context_t ctx, char *line) } + +static const char hlp_keywrap_key[] = + "KEYWRAP_KEY [--clear] \n" + "\n" + "Return a key to wrap another key. For now the key is returned\n" + "verbatim and and thus makes not much sense because an eavesdropper on\n" + "the gpg-agent connection will see the key as well as the wrapped key.\n" + "However, this function may either be equipped with a public key\n" + "mechanism or not used at all if the key is a pre-shared key. In any\n" + "case wrapping the import and export of keys is a requirement for\n" + "certain cryptographic validations and thus useful. The key persists\n" + "a RESET command but may be cleared using the option --clear.\n" + "\n" + "Supported modes are:\n" + " --import - Return a key to import a key into gpg-agent\n" + " --export - Return a key to export a key from gpg-agent"; +static gpg_error_t +cmd_keywrap_key (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + int clearopt = has_option (line, "--clear"); + + + assuan_begin_confidential (ctx); + if (has_option (line, "--import")) + { + xfree (ctrl->server_local->import_key); + if (clearopt) + ctrl->server_local->import_key = NULL; + else if (!(ctrl->server_local->import_key = + gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) + err = gpg_error_from_syserror (); + else + err = assuan_send_data (ctx, ctrl->server_local->import_key, + KEYWRAP_KEYSIZE); + } + else if (has_option (line, "--export")) + { + xfree (ctrl->server_local->export_key); + if (clearopt) + ctrl->server_local->export_key = NULL; + else if (!(ctrl->server_local->export_key = + gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) + err = gpg_error_from_syserror (); + else + err = assuan_send_data (ctx, ctrl->server_local->export_key, + KEYWRAP_KEYSIZE); + } + else + err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE"); + assuan_end_confidential (ctx); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_import_key[] = + "IMPORT_KEY\n" + "\n" + "Import a secret key into the key store. The key is expected to be\n" + "encrypted using the current session's key wrapping key (cf. command\n" + "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" + "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" + "key data. The unwrapped key must be a canonical S-expression."; +static gpg_error_t +cmd_import_key (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + gcry_cipher_hd_t cipherhd = NULL; + unsigned char *key = NULL; + size_t keylen, realkeylen; + char *passphrase = NULL; + unsigned char *finalkey = NULL; + size_t finalkeylen; + unsigned char grip[20]; + + (void)line; + + if (!ctrl->server_local->import_key) + { + err = gpg_error (GPG_ERR_BAD_KEY); + goto leave; + } + + assuan_begin_confidential (ctx); + err = assuan_inquire (ctx, "KEYDATA", + &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA); + assuan_end_confidential (ctx); + if (err) + goto leave; + if (wrappedkeylen < 24) + { + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + keylen = wrappedkeylen - 8; + key = xtrymalloc_secure (keylen); + if (!key) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipherhd, + ctrl->server_local->import_key, KEYWRAP_KEYSIZE); + if (err) + goto leave; + err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); + if (err) + goto leave; + gcry_cipher_close (cipherhd); + cipherhd = NULL; + xfree (wrappedkey); + wrappedkey = NULL; + + realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); + if (!realkeylen) + goto leave; /* Invalid canonical encoded S-expression. */ + + err = keygrip_from_canon_sexp (key, realkeylen, grip); + if (err) + goto leave; + + if (!agent_key_available (grip)) + { + err = gpg_error (GPG_ERR_EEXIST); + goto leave; + } + + err = agent_ask_new_passphrase + (ctrl, _("Please enter the passphrase to protect the " + "imported object within the GnuPG system."), + &passphrase); + if (err) + goto leave; + + if (passphrase) + { + err = agent_protect (key, passphrase, &finalkey, &finalkeylen); + if (!err) + err = agent_write_private_key (grip, finalkey, finalkeylen, 0); + } + else + err = agent_write_private_key (grip, key, realkeylen, 0); + + leave: + xfree (finalkey); + xfree (passphrase); + xfree (key); + gcry_cipher_close (cipherhd); + xfree (wrappedkey); + return leave_cmd (ctx, err); +} + + + +static const char hlp_export_key[] = + "EXPORT_KEY\n" + "\n"; +static gpg_error_t +cmd_export_key (assuan_context_t ctx, char *line) +{ + gpg_error_t err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + + /* leave: */ + return leave_cmd (ctx, err); +} + + + static const char hlp_getval[] = "GETVAL \n" @@ -1435,9 +1615,7 @@ cmd_getval (assuan_context_t ctx, char *line) else return gpg_error (GPG_ERR_NO_DATA); - if (rc) - log_error ("command getval failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1520,9 +1698,7 @@ cmd_putval (assuan_context_t ctx, char *line) } } - if (rc) - log_error ("command putval failed: %s\n", gpg_strerror (rc)); - return rc; + return leave_cmd (ctx, rc); } @@ -1641,7 +1817,7 @@ static const char hlp_getinfo[] = " std_session_env - List the standard session environment.\n" " std_startup_env - List the standard startup environment.\n" " cmd_has_option\n" - " - Returns OK if the command CMD implements the option OPT."; + " - Returns OK if the command CMD implements the option OPT\n."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { @@ -1910,6 +2086,9 @@ register_commands (assuan_context_t ctx) { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd, hlp_scd }, + { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key }, + { "IMPORT_KEY", cmd_import_key, hlp_import_key }, + { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty }, @@ -2021,6 +2200,9 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) /* Cleanup. */ assuan_release (ctx); + xfree (ctrl->server_local->keydesc); + xfree (ctrl->server_local->import_key); + xfree (ctrl->server_local->export_key); if (ctrl->server_local->stopme) agent_exit (0); xfree (ctrl->server_local); diff --git a/agent/findkey.c b/agent/findkey.c index d6478ac4d..db610c15a 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -69,7 +69,7 @@ agent_write_private_key (const unsigned char *grip, { log_error ("secret key file `%s' already exists\n", fname); xfree (fname); - return gpg_error (GPG_ERR_GENERAL); + return gpg_error (GPG_ERR_EEXIST); } /* FIXME: On POSIX systems we used include S_IRGRP as well. */ @@ -883,8 +883,8 @@ agent_public_key_from_file (ctrl_t ctrl, -/* Return the secret key as an S-Exp after locating it using the grip. - Returns NULL if key is not available. 0 = key is available */ +/* Check whether the the secret key identified by GRIP is available. + Returns 0 is the key is available. */ int agent_key_available (const unsigned char *grip) { diff --git a/agent/genkey.c b/agent/genkey.c index c5d2c9e33..7c6b44b96 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -1,5 +1,5 @@ /* genkey.c - Generate a keypair - * Copyright (C) 2002, 2003, 2004, 2007 Free Software Foundation, Inc. + * Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -286,6 +286,69 @@ reenter_compare_cb (struct pin_entry_info_s *pi) } +/* 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 = _("Please re-enter this passphrase"); + const char *initial_errtext = NULL; + struct pin_entry_info_s *pi, *pi2; + + *r_passphrase = NULL; + + pi = gcry_calloc_secure (2, sizeof (*pi) + 100); + pi2 = pi + (sizeof *pi + 100); + pi->max_length = 100; + pi->max_tries = 3; + pi->with_qualitybar = 1; + pi2->max_length = 100; + 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); + initial_errtext = NULL; + if (!err) + { + if (check_passphrase_constraints (ctrl, pi->pin, 0)) + { + pi->failed_tries = 0; + pi2->failed_tries = 0; + goto next_try; + } + /* Unless the passphrase is empty, ask to confirm it. */ + if (pi->pin && *pi->pin) + { + err = agent_askpin (ctrl, text2, NULL, NULL, pi2); + if (err == -1) + { /* The re-entered one did not match and the user did not + hit cancel. */ + initial_errtext = _("does not match - try again"); + goto next_try; + } + } + } + + if (!err && *pi->pin) + { + /* User wants a passphrase. */ + *r_passphrase = xtrystrdup (pi->pin); + if (!*r_passphrase) + err = gpg_error_from_syserror (); + } + xfree (pi); + return err; +} + + /* Generate a new keypair according to the parameters given in KEYPARAM */ @@ -294,7 +357,7 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; - struct pin_entry_info_s *pi, *pi2; + char *passphrase = NULL; int rc; size_t len; char *buf; @@ -307,63 +370,19 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, } /* Get the passphrase now, cause key generation may take a while. */ - { - const char *text1 = _("Please enter the passphrase to%0A" - "to protect your new key"); - const char *text2 = _("Please re-enter this passphrase"); - const char *initial_errtext = NULL; - - pi = gcry_calloc_secure (2, sizeof (*pi) + 100); - pi2 = pi + (sizeof *pi + 100); - pi->max_length = 100; - pi->max_tries = 3; - pi->with_qualitybar = 1; - pi2->max_length = 100; - pi2->max_tries = 3; - pi2->check_cb = reenter_compare_cb; - pi2->check_cb_arg = pi->pin; - - next_try: - rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi); - initial_errtext = NULL; - if (!rc) - { - if (check_passphrase_constraints (ctrl, pi->pin, 0)) - { - pi->failed_tries = 0; - pi2->failed_tries = 0; - goto next_try; - } - if (pi->pin && *pi->pin) - { - rc = agent_askpin (ctrl, text2, NULL, NULL, pi2); - if (rc == -1) - { /* The re-entered one did not match and the user did not - hit cancel. */ - initial_errtext = _("does not match - try again"); - goto next_try; - } - } - } - if (rc) - { - xfree (pi); - return rc; - } - - if (!*pi->pin) - { - xfree (pi); - pi = NULL; /* User does not want a passphrase. */ - } - } + rc = agent_ask_new_passphrase (ctrl, + _("Please enter the passphrase to%0A" + "to protect your new key"), + &passphrase); + if (rc) + return rc; 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 (pi); + xfree (passphrase); return rc; } @@ -373,7 +392,7 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_key); - xfree (pi); + xfree (passphrase); return gpg_error (GPG_ERR_INV_DATA); } s_public = gcry_sexp_find_token (s_key, "public-key", 0); @@ -382,7 +401,7 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_private); gcry_sexp_release (s_key); - xfree (pi); + xfree (passphrase); return gpg_error (GPG_ERR_INV_DATA); } gcry_sexp_release (s_key); s_key = NULL; @@ -390,8 +409,9 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, /* store the secret key */ if (DBG_CRYPTO) log_debug ("storing private key\n"); - rc = store_key (s_private, pi? pi->pin:NULL, 0); - xfree (pi); pi = NULL; + rc = store_key (s_private, passphrase, 0); + xfree (passphrase); + passphrase = NULL; gcry_sexp_release (s_private); if (rc) { @@ -423,65 +443,20 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen, -/* Apply a new passpahrse to the key S_SKEY and store it. */ +/* Apply a new passphrase to the key S_SKEY and store it. */ int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey) { - struct pin_entry_info_s *pi, *pi2; int rc; + char *passphrase; - { - const char *text1 = _("Please enter the new passphrase"); - const char *text2 = _("Please re-enter this passphrase"); - const char *initial_errtext = NULL; - - pi = gcry_calloc_secure (2, sizeof (*pi) + 100); - pi2 = pi + (sizeof *pi + 100); - pi->max_length = 100; - pi->max_tries = 3; - pi->with_qualitybar = 1; - pi2->max_length = 100; - pi2->max_tries = 3; - pi2->check_cb = reenter_compare_cb; - pi2->check_cb_arg = pi->pin; - - next_try: - rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi); - initial_errtext = NULL; - if (!rc) - { - if (check_passphrase_constraints (ctrl, pi->pin, 0)) - { - pi->failed_tries = 0; - pi2->failed_tries = 0; - goto next_try; - } - /* Unless the passphrase is empty, ask to confirm it. */ - if (pi->pin && *pi->pin) - { - rc = agent_askpin (ctrl, text2, NULL, NULL, pi2); - if (rc == -1) - { /* The re-entered one did not match and the user did not - hit cancel. */ - initial_errtext = _("does not match - try again"); - goto next_try; - } - } - } - if (rc) - { - xfree (pi); - return rc; - } - - if (!*pi->pin) - { - xfree (pi); - pi = NULL; /* User does not want a passphrase. */ - } - } - - rc = store_key (s_skey, pi? pi->pin:NULL, 1); - xfree (pi); + rc = agent_ask_new_passphrase (ctrl, + _("Please enter the new passphrase"), + &passphrase); + if (!rc) + { + rc = store_key (s_skey, passphrase, 1); + xfree (passphrase); + } return rc; } diff --git a/agent/preset-passphrase.c b/agent/preset-passphrase.c index ae202bf65..2037d9571 100644 --- a/agent/preset-passphrase.c +++ b/agent/preset-passphrase.c @@ -43,7 +43,6 @@ #define JNLIB_NEED_LOG_LOGV #include "agent.h" -#include "minip12.h" #include "simple-pwquery.h" #include "i18n.h" #include "sysutils.h" diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 38debb956..48186d2ac 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -40,7 +40,6 @@ #define JNLIB_NEED_LOG_LOGV #include "agent.h" -#include "minip12.h" #include "i18n.h" #include "get-passphrase.h" #include "sysutils.h" @@ -63,8 +62,6 @@ enum cmd_and_opt_values oS2Kcalibration, oCanonical, - oP12Import, - oP12Export, oP12Charset, oStore, oForce, @@ -116,11 +113,6 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_c (oShadow, "shadow", "create a shadow entry for a public key"), ARGPARSE_c (oShowShadowInfo, "show-shadow-info", "return the shadow info"), ARGPARSE_c (oShowKeygrip, "show-keygrip", "show the \"keygrip\""), - ARGPARSE_c (oP12Import, "p12-import", - "import a pkcs#12 encoded private key"), - ARGPARSE_c (oP12Export, "p12-export", - "export a private key pkcs#12 encoded"), - ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), @@ -635,7 +627,7 @@ rsa_key_check (struct rsa_secret_key_s *skey) return err? -1:0; } - +#if 0 /* A callback used by p12_parse to return a certificate. */ static void import_p12_cert_cb (void *opaque, const unsigned char *cert, size_t certlen) @@ -793,6 +785,7 @@ import_p12_file (const char *fname) xfree (result); } +#endif @@ -865,6 +858,7 @@ is_keygrip (const char *string) } +#if 0 static void export_p12_file (const char *fname) { @@ -1009,6 +1003,7 @@ export_p12_file (const char *fname) fwrite (key, keylen, 1, stdout); xfree (key); } +#endif @@ -1059,8 +1054,6 @@ main (int argc, char **argv ) case oShadow: cmd = oShadow; break; case oShowShadowInfo: cmd = oShowShadowInfo; break; case oShowKeygrip: cmd = oShowKeygrip; break; - case oP12Import: cmd = oP12Import; break; - case oP12Export: cmd = oP12Export; break; case oP12Charset: opt_p12_charset = pargs.r.ret_str; break; case oS2Kcalibration: cmd = oS2Kcalibration; break; @@ -1105,10 +1098,6 @@ main (int argc, char **argv ) show_shadow_info (fname); else if (cmd == oShowKeygrip) show_keygrip (fname); - else if (cmd == oP12Import) - import_p12_file (fname); - else if (cmd == oP12Export) - export_p12_file (fname); else if (cmd == oS2Kcalibration) { if (!opt.verbose) diff --git a/agent/protect.c b/agent/protect.c index 7f3c1cc42..db6caa48c 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -415,7 +415,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char *p; gcry_md_hd_t md; - /* Create an S-expression with the procted-at timestamp. */ + /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); gnupg_get_isotime (timestamp_exp+19); timestamp_exp[19+15] = ')'; diff --git a/common/ChangeLog b/common/ChangeLog index 5aa39914c..e5815738c 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,11 @@ +2010-06-17 Werner Koch + + * sexputil.c (make_canon_sexp_pad): New. + +2010-06-14 Werner Koch + + * membuf.c (put_membuf): Add shortcut for !LEN. + 2010-06-11 Marcus Brinkmann * sysutils.c (translate_sys2libc_fd): Revert last change. diff --git a/common/membuf.c b/common/membuf.c index dc8f6f692..f9f82d357 100644 --- a/common/membuf.c +++ b/common/membuf.c @@ -59,7 +59,7 @@ init_membuf_secure (membuf_t *mb, int initiallen) void put_membuf (membuf_t *mb, const void *buf, size_t len) { - if (mb->out_of_core) + if (mb->out_of_core || !len) return; if (mb->len + len >= mb->size) diff --git a/common/sexputil.c b/common/sexputil.c index 736caded3..b336145c4 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -36,7 +36,7 @@ #include "sexp-parse.h" -/* Helper function to create a a canonical encoded S-expression from a +/* Helper function to create a canonical encoded S-expression from a Libgcrypt S-expression object. The function returns 0 on success and the malloced canonical S-expression is stored at R_BUFFER and the allocated length at R_BUFLEN. On error an error code is @@ -71,6 +71,36 @@ make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen) } +/* Same as make_canon_sexp but pad the buffer to multiple of 64 + bits. */ +gpg_error_t +make_canon_sexp_pad (gcry_sexp_t sexp, + unsigned char **r_buffer, size_t *r_buflen) +{ + size_t len; + unsigned char *buf; + + *r_buffer = NULL; + if (r_buflen) + *r_buflen = 0;; + + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); + if (!len) + return gpg_error (GPG_ERR_BUG); + len += (8 - len % 8) % 8; + buf = xtrycalloc (1, len); + if (!buf) + return gpg_error_from_syserror (); + if (!gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len)) + return gpg_error (GPG_ERR_BUG); + + *r_buffer = buf; + if (r_buflen) + *r_buflen = len; + + return 0; +} + /* Return the so called "keygrip" which is the SHA-1 hash of the public key parameters expressed in a way depended on the algorithm. diff --git a/common/util.h b/common/util.h index 97ecef178..519bc5d68 100644 --- a/common/util.h +++ b/common/util.h @@ -146,6 +146,8 @@ gpg_error_t b64dec_finish (struct b64state *state); /*-- sexputil.c */ gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); +gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, + unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); diff --git a/dirmngr/http.c b/dirmngr/http.c index b10ba254e..b65a92adc 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -38,6 +38,8 @@ an exit handler to cleanup the socket layer. */ +#warning Duplicated code with common/http.c + #ifdef HAVE_CONFIG_H # include #endif diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index bb15766b5..34450474c 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -786,3 +786,254 @@ as a binary blob. @end ifset @include see-also-note.texi +@c +@c !!! UNDER CONSTRUCTION !!! +@c +@c +@c @section Verifying a Certificate +@c +@c There are several ways to request services from Dirmngr. Almost all of +@c them are done using the Assuan protocol. What we describe here is the +@c Assuan command CHECKCRL as used for example by the dirmnr-client tool if +@c invoked as +@c +@c @example +@c dirmngr-client foo.crt +@c @end example +@c +@c This command will send an Assuan request to an already running Dirmngr +@c instance. foo.crt is expected to be a standard X.509 certificate and +@c dirmngr will receive the Assuan command +@c +@c @example +@c CHECKCRL @var [{fingerprint}] +@c @end example +@c +@c @var{fingerprint} is optional and expected to be the SHA-1 has of the +@c DER encoding of the certificate under question. It is to be HEX +@c encoded. The rationale for sending the fingerprint is that it allows +@c dirmngr to reply immediatly if it has already cached such a request. If +@c this is not the case and no certificate has been found in dirmngr's +@c internal certificate storage, dirmngr will request the certificate using +@c the Assuan inquiry +@c +@c @example +@c INQUIRE TARGETCERT +@c @end example +@c +@c The caller (in our example dirmngr-client) is then expected to return +@c the certificate for the request (which should match @var{fingerprint}) +@c as a binary blob. +@c +@c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This +@c function checks whether a CRL item exists for target certificate. These +@c CRL items are kept in a database of already loaded and verified CRLs. +@c This mechanism is called the CRL cache. Obviously timestamps are kept +@c there with each item to cope with the expiration date of the CRL. The +@c possible return values are: @code{0} to indicate that a valid CRL is +@c available for the certificate and the certificate itself is not listed +@c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is +@c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no +@c information is available. The first two codes are immediatly returned to +@c the caller and the processing of this request has been done. +@c +@c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now +@c calls @code{clr_cache_reload_crl} and if this succeeds calls +@c @code{crl_cache_cert_isvald) once more. All further errors are +@c immediately returned to the caller. +@c +@c @code{crl_cache_reload_crl} is the actual heart of the CRL management. +@c It locates the corresponding CRL for the target certificate, reads and +@c verifies this CRL and stores it in the CRL cache. It works like this: +@c +@c * Loop over all crlDPs in the target certificate. +@c * If the crlDP is invalid immediately terminate the loop. +@c * Loop over all names in the current crlDP. +@c * If the URL scheme is unknown or not enabled +@c (--ignore-http-dp, --ignore-ldap-dp) continues with +@c the next name. +@c * @code{crl_fetch} is called to actually retrieve the CRL. +@c In case of problems this name is ignore and we continue with +@c the next name. Note that @code{crl_fetch} does only return +@c a descriptor for the CRL for further reading so does the CRL +@c does not yet end up in memory. +@c * @code{crl_cache_insert} is called with that descriptor to +@c actually read the CRL into the cache. See below for a +@c description of this function. If there is any error (e.g. read +@c problem, CRL not correctly signed or verification of signature +@c not possible), this descriptor is rejected and we continue +@c with the next name. If the CRL has been successfully loaded, +@c the loop is terminated. +@c * If no crlDP has been found in the previous loop use a default CRL. +@c Note, that if any crlDP has been found but loading of the CRL failed, +@c this condition is not true. +@c * Try to load a CRL from all configured servers (ldapservers.conf) +@c in turn. The first server returning a CRL is used. +@c * @code(crl_cache_insert) is then used to actually insert the CRL +@c into the cache. If this failed we give up immediatley without +@c checking the rest of the servers from the first step. +@c * Ready. +@c +@c +@c The @code{crl_cache_insert} function takes care of reading the bulk of +@c the CRL, parsing it and checking the signature. It works like this: A +@c new database file is created using a temporary file name. The CRL +@c parsing machinery is started and all items of the CRL are put into +@c this database file. At the end the issuer certificate of the CRL +@c needs to be retrieved. Three cases are to be distinguished: +@c +@c a) An authorityKeyIdentifier with an issuer and serialno exits: The +@c certificate is retrieved using @code{find_cert_bysn}. If +@c the certificate is in the certificate cache, it is directly +@c returned. Then the requester (i.e. the client who requested the +@c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether +@c he can provide this certificate. If this succeed the returned +@c certificate gets cached and returned. Note, that dirmngr does not +@c verify in any way whether the expected certificate is returned. +@c It is in the interest of the client to return a useful certificate +@c as otherwise the service request will fail due to a bad signature. +@c The last way to get the certificate is by looking it up at +@c external resources. This is done using the @code{ca_cert_fetch} +@c and @code{fetch_next_ksba_cert} and comparing the returned +@c certificate to match the requested issuer and seriano (This is +@c needed because the LDAP layer may return several certificates as +@c LDAP as no standard way to retrieve by serial number). +@c +@c b) An authorityKeyIdentifier with a key ID exists: The certificate is +@c retrieved using @code{find_cert_bysubject}. If the certificate is +@c in the certificate cache, it is directly returned. Then the +@c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether +@c he can provide this certificate. If this succeed the returned +@c certificate gets cached and returned. Note, that dirmngr does not +@c verify in any way whether the expected certificate is returned. +@c It is in the interest of the client to return a useful certificate +@c as otherwise the service request will fail due to a bad signature. +@c The last way to get the certificate is by looking it up at +@c external resources. This is done using the @code{ca_cert_fetch} +@c and @code{fetch_next_ksba_cert} and comparing the returned +@c certificate to match the requested subject and key ID. +@c +@c c) No authorityKeyIdentifier exits: The certificate is retrieved +@c using @code{find_cert_bysubject} without the key ID argument. If +@c the certificate is in the certificate cache the first one with a +@c matching subject is is directly returned. Then the requester is +@c asked via the Assuan inquiry ``SENDCERT'' and an exact +@c specification of the subject whether he can +@c provide this certificate. If this succeed the returned +@c certificate gets cached and returned. Note, that dirmngr does not +@c verify in any way whether the expected certificate is returned. +@c It is in the interest of the client to return a useful certificate +@c as otherwise the service request will fail due to a bad signature. +@c The last way to get the certificate is by looking it up at +@c external resources. This is done using the @code{ca_cert_fetch} +@c and @code{fetch_next_ksba_cert} and comparing the returned +@c certificate to match the requested subject; the first certificate +@c with a matching subject is then returned. +@c +@c If no certificate was found, the function returns with the error +@c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails, +@c the erro is returned. On success the @code{validate_cert_chain} is +@c used to verify that the certificate is actually valid. +@c +@c Here we may encounter a recursive situation: +@c @code{validate_cert_chain} needs to look at other certificates and +@c also at CRLs to check whether tehse other certificates and well, the +@c CRL issuer certificate itself are not revoked. FIXME: We need to make +@c sure that @code{validate_cert_chain} does not try to lookup the CRL we +@c are currently processing. This would be a catch-22 and may indicate a +@c broken PKI. However, due to overlapping expiring times and imprecise +@c clocks thsi may actually happen. +@c +@c For historical reasons the Assuan command ISVALID is a bit different +@c to CHECKCRL but this is mainly due to different calling conventions. +@c In the end the same fucntionality is used, albeit hidden by a couple +@c of indirection and argument and result code mangling. It furthere +@c ingetrages OCSP checking depending on options are the way it is +@c called. GPGSM still uses this command but might eventuall switch over +@c to CHECKCRL and CHECKOCSP so that ISVALID can be retired. +@c +@c +@c @section Validating a certificate +@c +@c We describe here how the internal function @code{validate_cert_chain} +@c works. Note that mainly testing purposes this functionality may be +@c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}. +@c +@c For backward compatibility this function returns success if Dirmngr is +@c not used as a system daemon. Thus not validating the certicates at +@c all. FIXME: This is definitely not correct and should be fixed ASAP. +@c +@c The function takes the target certificate and a mode argument as +@c parameters and returns an error code and optionally the closes +@c expiration time of all certificates in the chain. +@c +@c We first check that the certificate may be used for the requested +@c purpose (i.e. OCSP or CRL signing). If this is not the case +@c GPG_ERR_WRONG_KEY_USAGE is returned. +@c +@c The next step is to find the trust anchor (root certificate) and to +@c assemble the chain in memory: Starting with the target certificate, +@c the expiration time is checked against the current date, unknown +@c critical extensions are detected and certificate policies are matched +@c (We only allow 2.289.9.9 but I have no clue about that OID and from +@c where I got it - it does not even seem to be assigned - debug cruft?). +@c +@c Now if this certificate is a self-signed one, we have reached the +@c trust anchor. In this case we check that the signature is good, the +@c certificate is allowed to act as a CA, that it is a trusted one (by +@c checking whether it is has been put into the trusted-certs +@c configuration directory) and finally prepend into to our list +@c representing the certificate chain. This steps ends then. +@c +@c If it is not a self-signed certificate, we check that the chain won't +@c get too long (current limit is 100), if this is the case we terminate +@c with the error GPG_ERR_BAD_CERT_CHAIN. +@c +@c Now the issuer's certificate is looked up: If an +@c authorityKeyIdentifier is available, this one is used to locate the +@c certificate either using issuer and serialnumber or subject DN +@c (i.e. the issuer's DN) and the keyID. The functions +@c @code{find_cert_bysn) and @code{find_cert_bysubject} are used +@c respectively. The have already been described above under the +@c description of @code{crl_cache_insert}. If no certificate was found +@c or with no authorityKeyIdentifier, only the cache is consulted using +@c @code{get_cert_bysubject}. The latter is is done under the assumption +@c that a matching certificate has explicitly been put into the +@c certificate cache. If the issuer's certificate could not be found, +@c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}. +@c +@c If the issuer's certificate has been found, the signature of the +@c actual certificate is checked and in case this fails the error +@c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the +@c maximum cahin length of the issueing certificate is checked as well as +@c the capiblity of the certificate (i.e. whether he may be used for +@c certificate signing). Then the certificate is prepended to our list +@c representing the certificate chain. Finally the loop is continued now +@c with the issuer's certificate as the current certificate. +@c +@c After the end of the loop and if no error as been encountered +@c (i.e. the certificate chain has been assempled correctly), a check is +@c done whether any certificate expired or a critical policy has not been +@c met. In any of these cases the validation terminates with an +@c appropriate error. +@c +@c Finally the function @code{check_revocations} is called to verify no +@c certificate in the assempled chain has been revoked: This is an +@c recursive process because a CRL has to be checked for each certificate +@c in the chain except for the root certificate, of which we already know +@c that it is trusted and we avoid checking a CRL here due to common +@c setup problems and the assumption that a revoked root certifcate has +@c been removed from the list of trusted certificates. +@c +@c +@c +@c +@c @section Looking up certificates through LDAP. +@c +@c This describes the LDAP layer to retrieve certificates. +@c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are +@c used for this. The first one starts a search and the second one is +@c used to retrieve certificate after certificate. +@c + + diff --git a/g10/ChangeLog b/g10/ChangeLog index c661be1c9..e54a8edfc 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,8 @@ +2010-06-17 Werner Koch + + * gpg.c (main): Use CAST5 as default s2k algo. The macro + USE_CAST5 was only used with GnuPG 1.x. + 2010-06-07 Werner Koch * cpr.c: Use estream for status output. diff --git a/g10/gpg.c b/g10/gpg.c index 2b7b4be6d..909736dc3 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1976,11 +1976,7 @@ main (int argc, char **argv) opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */ opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_count = 0; /* Auto-calibrate when needed. */ -#ifdef USE_CAST5 opt.s2k_cipher_algo = CIPHER_ALGO_CAST5; -#else - opt.s2k_cipher_algo = CIPHER_ALGO_3DES; -#endif opt.completes_needed = 1; opt.marginals_needed = 3; opt.max_cert_depth = 5; diff --git a/sm/ChangeLog b/sm/ChangeLog index ebcd30589..686baca3a 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,19 @@ +2010-06-17 Werner Koch + + * import.c (parse_p12): Remove arg retfp. Use the agent's new + import command. + (import_one): Adjust call to pkcs12. + (store_cert_cb, rsa_key_check): New. + (popen_protect_tool): Remove. + * minip12.c (parse_bag_encrypted_data, p12_parse): Add arg + R_BADPASS. + * call-agent.c (gpgsm_agent_ask_passphrase): New. + (gpgsm_agent_keywrap_key): New. + (struct import_key_parm_s): New. + (gpgsm_agent_import_key): New. + * minip12.c, minip12.h: Move from ../agent/. + * Makefile.am (gpgsm_SOURCES): Add them. + 2010-06-11 Marcus Brinkmann * server.c (cmd_message) [HAVE_W32CE_SYSTEM]: Finish pipe. diff --git a/sm/Makefile.am b/sm/Makefile.am index 2754b8535..7386debcb 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -49,6 +49,7 @@ gpgsm_SOURCES = \ delete.c \ certreqgen.c \ certreqgen-ui.c \ + minip12.c minip12.h \ qualified.c diff --git a/sm/call-agent.c b/sm/call-agent.c index 402cb7dd0..e77f03847 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -1,6 +1,6 @@ /* call-agent.c - Divert GPGSM operations to the agent * Copyright (C) 2001, 2002, 2003, 2005, 2007, - * 2008, 2009 Free Software Foundation, Inc. + * 2008, 2009, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -66,6 +66,14 @@ struct learn_parm_s membuf_t *data; }; +struct import_key_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + const void *key; + size_t keylen; +}; + /* Try to connect to the agent via socket or fork it off and work by @@ -1066,3 +1074,130 @@ gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno) return err; } + + +/* Ask for the passphrase (this is used for pkcs#12 import/export. On + success the caller needs to free the string stored at R_PASSPHRASE. + On error NULL will be stored at R_PASSPHRASE and an appropriate + error code returned. */ +gpg_error_t +gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, + char **r_passphrase) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *arg4 = NULL; + membuf_t data; + + *r_passphrase = NULL; + + err = start_agent (ctrl); + if (err) + return err; + + if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg))) + return gpg_error_from_syserror (); + + snprintf (line, DIM(line)-1, "GET_PASSPHRASE --data -- X X X %s", arg4); + xfree (arg4); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + membuf_data_cb, &data, + default_inq_cb, NULL, NULL, NULL); + + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + *r_passphrase = get_membuf (&data, NULL); + if (!*r_passphrase) + err = gpg_error_from_syserror (); + } + return err; +} + + + +/* Retrieve a key encryption key from the agent. With FOREXPORT true + the key shall be use for export, with false for import. On success + the new key is stored at R_KEY and its length at R_KEKLEN. */ +gpg_error_t +gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, + void **r_kek, size_t *r_keklen) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + + *r_kek = NULL; + err = start_agent (ctrl); + if (err) + return err; + + snprintf (line, DIM(line)-1, "KEYWRAP_KEY %s", + forexport? "--export":"--import"); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + membuf_data_cb, &data, + default_inq_cb, ctrl, NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_kek = buf; + *r_keklen = len; + return 0; +} + + + + +/* Handle the inquiry for an IMPORT_KEY command. */ +static gpg_error_t +inq_import_key_parms (void *opaque, const char *line) +{ + struct import_key_parm_s *parm = opaque; + gpg_error_t err; + + if (!strncmp (line, "KEYDATA", 7) && (line[7]==' '||!line[7])) + { + assuan_begin_confidential (parm->ctx); + err = assuan_send_data (parm->ctx, parm->key, parm->keylen); + assuan_end_confidential (parm->ctx); + } + else + err = default_inq_cb (parm->ctrl, line); + + return err; +} + + +/* Call the agent to import a key into the agent. */ +gpg_error_t +gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen) +{ + gpg_error_t err; + struct import_key_parm_s parm; + + err = start_agent (ctrl); + if (err) + return err; + + parm.ctrl = ctrl; + parm.ctx = agent_ctx; + parm.key = key; + parm.keylen = keylen; + + err = assuan_transact (agent_ctx, "IMPORT_KEY", + NULL, NULL, inq_import_key_parms, &parm, NULL, NULL); + return err; +} diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 542c292ac..f065bfa11 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -405,6 +405,12 @@ gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc); gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl); gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno); +gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, + char **r_passphrase); +gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, + void **r_kek, size_t *r_keklen); +gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl, + const void *key, size_t keylen); /*-- call-dirmngr.c --*/ int gpgsm_dirmngr_isvalid (ctrl_t ctrl, diff --git a/sm/import.c b/sm/import.c index 6a012ca66..c920ac51a 100644 --- a/sm/import.c +++ b/sm/import.c @@ -35,6 +35,11 @@ #include "i18n.h" #include "sysutils.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ +#include "../common/membuf.h" +#include "minip12.h" + +/* The arbitrary limit of one PKCS#12 object. */ +#define MAX_P12OBJ_SIZE 128 /*kb*/ struct stats_s { @@ -48,8 +53,19 @@ struct stats_s { }; +struct rsa_secret_key_s +{ + gcry_mpi_t n; /* public modulus */ + gcry_mpi_t e; /* public exponent */ + gcry_mpi_t d; /* exponent */ + gcry_mpi_t p; /* prime p. */ + gcry_mpi_t q; /* prime q. */ + gcry_mpi_t u; /* inverse of p mod q. */ +}; + + static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, - estream_t *retfp, struct stats_s *stats); + struct stats_s *stats); @@ -325,51 +341,11 @@ import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd) any = 1; } else if (ct == KSBA_CT_PKCS12) - { /* This seems to be a pkcs12 message. We use an external - tool to parse the message and to store the private keys. - We need to use a another reader here to parse the - certificate we included in the p12 file; then we continue - to look for other pkcs12 files (works only if they are in - PEM format. */ - estream_t certfp; - Base64Context b64p12rdr; - ksba_reader_t p12rdr; - - rc = parse_p12 (ctrl, reader, &certfp, stats); + { + /* This seems to be a pkcs12 message. */ + rc = parse_p12 (ctrl, reader, stats); if (!rc) - { - any = 1; - - es_rewind (certfp); - rc = gpgsm_create_reader (&b64p12rdr, ctrl, certfp, 1, &p12rdr); - if (rc) - { - log_error ("can't create reader: %s\n", gpg_strerror (rc)); - es_fclose (certfp); - goto leave; - } - - do - { - ksba_cert_release (cert); cert = NULL; - rc = ksba_cert_new (&cert); - if (!rc) - { - rc = ksba_cert_read_der (cert, p12rdr); - if (!rc) - check_and_store (ctrl, stats, cert, 0); - } - ksba_reader_clear (p12rdr, NULL, NULL); - } - while (!rc && !gpgsm_reader_eof_seen (b64p12rdr)); - - if (gpg_err_code (rc) == GPG_ERR_EOF) - rc = 0; - gpgsm_destroy_reader (b64p12rdr); - es_fclose (certfp); - if (rc) - goto leave; - } + any = 1; } else if (ct == KSBA_CT_NONE) { /* Failed to identify this message - assume a certificate */ @@ -578,213 +554,363 @@ gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, } -/* Fork and exec the protect tool, connect the file descriptor of - INFILE to stdin, return a new estream in STATUSFILE, write the - output to OUTFILE and the pid of the process in PID. Returns 0 on - success or an error code. */ +/* Check that the RSA secret key SKEY is valid. Swap parameters to + the libgcrypt standard. */ static gpg_error_t -popen_protect_tool (ctrl_t ctrl, const char *pgmname, - estream_t infile, estream_t outfile, - estream_t *statusfile, pid_t *pid) +rsa_key_check (struct rsa_secret_key_s *skey) { - const char *argv[22]; - int i=0; + int err = 0; + gcry_mpi_t t = gcry_mpi_snew (0); + gcry_mpi_t t1 = gcry_mpi_snew (0); + gcry_mpi_t t2 = gcry_mpi_snew (0); + gcry_mpi_t phi = gcry_mpi_snew (0); - /* Make sure that the agent is running so that the protect tool is - able to ask for a passphrase. This has only an effect under W32 - where the agent is started on demand; sending a NOP does not harm - on other platforms. This is not really necessary anymore because - the protect tool does this now by itself; it does not harm either. */ - gpgsm_agent_send_nop (ctrl); - - argv[i++] = "--homedir"; - argv[i++] = opt.homedir; - argv[i++] = "--p12-import"; - argv[i++] = "--store"; - argv[i++] = "--no-fail-on-exist"; - argv[i++] = "--enable-status-msg"; - if (opt.fixed_passphrase) + /* Check that n == p * q. */ + gcry_mpi_mul (t, skey->p, skey->q); + if (gcry_mpi_cmp( t, skey->n) ) { - argv[i++] = "--passphrase"; - argv[i++] = opt.fixed_passphrase; + log_error ("RSA oops: n != p * q\n"); + err++; } - if (opt.agent_program) - { - argv[i++] = "--agent-program"; - argv[i++] = opt.agent_program; - } - argv[i++] = "--", - argv[i] = NULL; - assert (i < sizeof argv); - return gnupg_spawn_process (pgmname, argv, infile, outfile, - setup_pinentry_env, (128 | 64), - statusfile, pid); + /* Check that p is less than q. */ + if (gcry_mpi_cmp (skey->p, skey->q) > 0) + { + gcry_mpi_t tmp; + + log_info ("swapping secret primes\n"); + tmp = gcry_mpi_copy (skey->p); + gcry_mpi_set (skey->p, skey->q); + gcry_mpi_set (skey->q, tmp); + gcry_mpi_release (tmp); + /* Recompute u. */ + gcry_mpi_invm (skey->u, skey->p, skey->q); + } + + /* Check that e divides neither p-1 nor q-1. */ + gcry_mpi_sub_ui (t, skey->p, 1 ); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0) ) + { + log_error ("RSA oops: e divides p-1\n"); + err++; + } + gcry_mpi_sub_ui (t, skey->q, 1); + gcry_mpi_div (NULL, t, t, skey->e, 0); + if (!gcry_mpi_cmp_ui( t, 0)) + { + log_info ("RSA oops: e divides q-1\n" ); + err++; + } + + /* Check that d is correct. */ + gcry_mpi_sub_ui (t1, skey->p, 1); + gcry_mpi_sub_ui (t2, skey->q, 1); + gcry_mpi_mul (phi, t1, t2); + gcry_mpi_invm (t, skey->e, phi); + if (gcry_mpi_cmp (t, skey->d)) + { + /* No: try universal exponent. */ + gcry_mpi_gcd (t, t1, t2); + gcry_mpi_div (t, NULL, phi, t, 0); + gcry_mpi_invm (t, skey->e, t); + if (gcry_mpi_cmp (t, skey->d)) + { + log_error ("RSA oops: bad secret exponent\n"); + err++; + } + } + + /* Check for correctness of u. */ + gcry_mpi_invm (t, skey->p, skey->q); + if (gcry_mpi_cmp (t, skey->u)) + { + log_info ("RSA oops: bad u parameter\n"); + err++; + } + + if (err) + log_info ("RSA secret key check failed\n"); + + gcry_mpi_release (t); + gcry_mpi_release (t1); + gcry_mpi_release (t2); + gcry_mpi_release (phi); + + return err? gpg_error (GPG_ERR_BAD_SECKEY):0; +} + + +/* Object passed to store_cert_cb. */ +struct store_cert_parm_s +{ + gpg_error_t err; /* First error seen. */ + struct stats_s *stats; /* The stats object. */ + ctrl_t ctrl; /* The control object. */ +}; + +/* Helper to store the DER encoded certificate CERTDATA of length + CERTDATALEN. */ +static void +store_cert_cb (void *opaque, + const unsigned char *certdata, size_t certdatalen) +{ + struct store_cert_parm_s *parm = opaque; + gpg_error_t err; + ksba_cert_t cert; + + err = ksba_cert_new (&cert); + if (err) + { + if (!parm->err) + parm->err = err; + return; + } + + err = ksba_cert_init_from_mem (cert, certdata, certdatalen); + if (err) + { + log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); + if (!parm->err) + parm->err = err; + } + else + check_and_store (parm->ctrl, parm->stats, cert, 0); + ksba_cert_release (cert); } /* Assume that the reader is at a pkcs#12 message and try to import - certificates from that stupid format. We will also store secret - keys. All of the pkcs#12 parsing and key storing is handled by the - gpg-protect-tool, we merely have to take care of receiving the - certificates. On success RETFP returns a stream to a temporary - file with certificates. */ + certificates from that stupid format. We will transfer secret + keys to the agent. */ static gpg_error_t -parse_p12 (ctrl_t ctrl, ksba_reader_t reader, - estream_t *retfp, struct stats_s *stats) +parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats) { - const char *pgmname; - gpg_error_t err = 0, child_err = 0; - int c, cont_line; - unsigned int pos; - estream_t tmpfp; - estream_t fp = NULL; - estream_t certfp = NULL; + gpg_error_t err = 0; char buffer[1024]; - size_t nread; - pid_t pid = -1; + size_t ntotal, nread; + membuf_t p12mbuf; + char *p12buffer = NULL; + size_t p12buflen; + size_t p12bufoff; + gcry_mpi_t *kparms = NULL; + struct rsa_secret_key_s sk; + char *passphrase = NULL; + unsigned char *key = NULL; + size_t keylen; + void *kek = NULL; + size_t keklen; + unsigned char *wrappedkey = NULL; + size_t wrappedkeylen; + gcry_cipher_hd_t cipherhd = NULL; + gcry_sexp_t s_key = NULL; + unsigned char grip[20]; int bad_pass = 0; + int i; + struct store_cert_parm_s store_cert_parm; - if (!opt.protect_tool_program || !*opt.protect_tool_program) - pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); - else - pgmname = opt.protect_tool_program; + memset (&store_cert_parm, 0, sizeof store_cert_parm); + store_cert_parm.ctrl = ctrl; + store_cert_parm.stats = stats; - *retfp = NULL; - - /* To avoid an extra feeder process or doing selects and because - gpg-protect-tool will anyway parse the entire pkcs#12 message in - memory, we simply use tempfiles here and pass them to - the gpg-protect-tool. */ - tmpfp = es_tmpfile (); - if (!tmpfp) - { - err = gpg_error_from_syserror (); - log_error (_("error creating temporary file: %s\n"), strerror (errno)); - goto cleanup; - } + init_membuf (&p12mbuf, 4096); + ntotal = 0; while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread))) { - if (nread && es_fwrite (buffer, nread, 1, tmpfp) != 1) + if (ntotal >= MAX_P12OBJ_SIZE*1024) { - err = gpg_error_from_syserror (); - log_error (_("error writing to temporary file: %s\n"), - strerror (errno)); - goto cleanup; + /* Arbitrary limit to avoid DoS attacks. */ + err = gpg_error (GPG_ERR_TOO_LARGE); + log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE); + break; } + put_membuf (&p12mbuf, buffer, nread); + ntotal += nread; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; + if (!err) + { + p12buffer = get_membuf (&p12mbuf, &p12buflen); + if (!p12buffer) + err = gpg_error_from_syserror (); + } if (err) { log_error (_("error reading input: %s\n"), gpg_strerror (err)); - goto cleanup; + goto leave; } - certfp = es_tmpfile (); - if (!certfp) + /* GnuPG 2.0.4 accidently created binary P12 files with the string + "The passphrase is %s encoded.\n\n" prepended to the ASN.1 data. + We fix that here. */ + if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18)) { - err = gpg_error_from_syserror (); - log_error (_("error creating temporary file: %s\n"), strerror (errno)); - goto cleanup; - } - - err = popen_protect_tool (ctrl, pgmname, tmpfp, certfp, &fp, &pid); - if (err) - { - pid = -1; - goto cleanup; - } - es_fclose (tmpfp); - tmpfp = NULL; - - /* Read stderr of the protect tool. */ - pos = 0; - cont_line = 0; - while ((c=es_getc (fp)) != EOF) - { - /* fixme: We could here grep for status information of the - protect tool to figure out better error codes for - CHILD_ERR. */ - buffer[pos++] = c; - if (pos >= sizeof buffer - 5 || c == '\n') - { - buffer[pos - (c == '\n')] = 0; - if (cont_line) - log_printf ("%s", buffer); - else - { - if (!strncmp (buffer, "gpg-protect-tool: [PROTECT-TOOL:] ",34)) - { - char *p, *pend; - - p = buffer + 34; - pend = strchr (p, ' '); - if (pend) - *pend = 0; - if ( !strcmp (p, "secretkey-stored")) - { - stats->count++; - stats->secret_read++; - stats->secret_imported++; - } - else if ( !strcmp (p, "secretkey-exists")) - { - stats->count++; - stats->secret_read++; - stats->secret_dups++; - } - else if ( !strcmp (p, "bad-passphrase")) - { - - } - } - else - { - log_info ("%s", buffer); - if (!strncmp (buffer, "gpg-protect-tool: " - "possibly bad passphrase given",46)) - bad_pass++; - } - } - pos = 0; - cont_line = (c != '\n'); - } - } - - if (pos) - { - buffer[pos] = 0; - if (cont_line) - log_printf ("%s\n", buffer); - else - log_info ("%s\n", buffer); - } - - - /* If we found no error in the output of the child, setup a suitable - error code, which will later be reset if the exit status of the - child is 0. */ - if (!child_err) - child_err = gpg_error (GPG_ERR_DECRYPT_FAILED); - - cleanup: - es_fclose (tmpfp); - es_fclose (fp); - if (pid != -1) - { - if (!gnupg_wait_process (pgmname, pid, 0, NULL)) - child_err = 0; - gnupg_release_process (pid); - } - if (!err) - err = child_err; - if (err) - { - es_fclose (certfp); + for (p12bufoff=18; + p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n'; + p12bufoff++) + ; + p12bufoff++; + if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n') + p12bufoff++; } else - *retfp = certfp; + p12bufoff = 0; + + + err = gpgsm_agent_ask_passphrase + (ctrl, _("Please enter the passphrase to unprotect the PKCS#12 object."), + &passphrase); + if (err) + goto leave; + + kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff, + passphrase, store_cert_cb, &store_cert_parm, &bad_pass); + + xfree (passphrase); + passphrase = NULL; + + if (!kparms) + { + log_error ("error parsing or decrypting the PKCS#12 file\n"); + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + +/* print_mpi (" n", kparms[0]); */ +/* print_mpi (" e", kparms[1]); */ +/* print_mpi (" d", kparms[2]); */ +/* print_mpi (" p", kparms[3]); */ +/* print_mpi (" q", kparms[4]); */ +/* print_mpi ("dmp1", kparms[5]); */ +/* print_mpi ("dmq1", kparms[6]); */ +/* print_mpi (" u", kparms[7]); */ + + sk.n = kparms[0]; + sk.e = kparms[1]; + sk.d = kparms[2]; + sk.q = kparms[3]; + sk.p = kparms[4]; + sk.u = kparms[7]; + err = rsa_key_check (&sk); + if (err) + goto leave; +/* print_mpi (" n", sk.n); */ +/* print_mpi (" e", sk.e); */ +/* print_mpi (" d", sk.d); */ +/* print_mpi (" p", sk.p); */ +/* print_mpi (" q", sk.q); */ +/* print_mpi (" u", sk.u); */ + + /* Create an S-expresion from the parameters. */ + err = gcry_sexp_build (&s_key, NULL, + "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", + sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL); + for (i=0; i < 8; i++) + gcry_mpi_release (kparms[i]); + gcry_free (kparms); + kparms = NULL; + if (err) + { + log_error ("failed to created S-expression from key: %s\n", + gpg_strerror (err)); + goto leave; + } + + /* Compute the keygrip. */ + if (!gcry_pk_get_keygrip (s_key, grip)) + { + err = gpg_error (GPG_ERR_GENERAL); + log_error ("can't calculate keygrip\n"); + goto leave; + } + log_printhex ("keygrip=", grip, 20); + + /* Convert to canonical encoding using a function which pads it to a + multiple of 64 bits. We need this padding for AESWRAP. */ + err = make_canon_sexp_pad (s_key, &key, &keylen); + if (err) + { + log_error ("error creating canonical S-expression\n"); + goto leave; + } + gcry_sexp_release (s_key); + s_key = NULL; + + /* Get the current KEK. */ + err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen); + if (err) + { + log_error ("error getting the KEK: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Wrap the key. */ + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + goto leave; + err = gcry_cipher_setkey (cipherhd, kek, keklen); + if (err) + goto leave; + xfree (kek); + kek = NULL; + + wrappedkeylen = keylen + 8; + wrappedkey = xtrymalloc (wrappedkeylen); + if (!wrappedkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); + if (err) + goto leave; + xfree (key); + key = NULL; + gcry_cipher_close (cipherhd); + cipherhd = NULL; + + /* Send the wrapped key to the agent. */ + err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen); + if (!err) + { + stats->count++; + stats->secret_read++; + stats->secret_imported++; + } + else if ( gpg_err_code (err) == GPG_ERR_EEXIST ) + { + err = 0; + stats->count++; + stats->secret_read++; + stats->secret_dups++; + } + + /* If we did not get an error from storing the secret key we return + a possible error from parsing the certificates. We do this after + storing the secret keys so that a bad certificate does not + inhibit our chance to store the secret key. */ + if (!err && store_cert_parm.err) + err = store_cert_parm.err; + + leave: + if (kparms) + { + for (i=0; i < 8; i++) + gcry_mpi_release (kparms[i]); + gcry_free (kparms); + kparms = NULL; + } + xfree (key); + gcry_sexp_release (s_key); + xfree (passphrase); + gcry_cipher_close (cipherhd); + xfree (wrappedkey); + xfree (kek); + xfree (get_membuf (&p12mbuf, NULL)); + xfree (p12buffer); if (bad_pass) { diff --git a/agent/minip12.c b/sm/minip12.c similarity index 99% rename from agent/minip12.c rename to sm/minip12.c index 6f512e9e5..f50fbd419 100644 --- a/agent/minip12.c +++ b/sm/minip12.c @@ -608,7 +608,8 @@ static int parse_bag_encrypted_data (const unsigned char *buffer, size_t length, int startoffset, size_t *r_consumed, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), - void *certcbarg, gcry_mpi_t **r_result) + void *certcbarg, gcry_mpi_t **r_result, + int *r_badpass) { struct tag_info ti; const unsigned char *p = buffer; @@ -1003,6 +1004,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length, to check for a bad passphrase; it should therefore not be translated or changed. */ log_error ("possibly bad passphrase given\n"); + *r_badpass = 1; } return -1; } @@ -1277,7 +1279,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, gcry_mpi_t * p12_parse (const unsigned char *buffer, size_t length, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), - void *certcbarg) + void *certcbarg, int *r_badpass) { struct tag_info ti; const unsigned char *p = buffer; @@ -1289,6 +1291,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, gcry_mpi_t *result = NULL; unsigned char *cram_buffer = NULL; + *r_badpass = 0; where = "pfx"; if (parse_tag (&p, &n, &ti)) goto bailout; @@ -1384,7 +1387,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw, where = "bag.encryptedData"; if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, certcb, certcbarg, - result? NULL : &result)) + result? NULL : &result, r_badpass)) goto bailout; if (lenndef) len += consumed; diff --git a/agent/minip12.h b/sm/minip12.h similarity index 95% rename from agent/minip12.h rename to sm/minip12.h index 998f82f6a..f2af70957 100644 --- a/agent/minip12.h +++ b/sm/minip12.h @@ -25,7 +25,7 @@ gcry_mpi_t *p12_parse (const unsigned char *buffer, size_t length, const char *pw, void (*certcb)(void*, const unsigned char*, size_t), - void *certcbarg); + void *certcbarg, int *r_badpass); unsigned char *p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,