From 006fd75aea5cc766bc223e435e5a07b543d658d3 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 17 Jun 2010 15:44:44 +0000 Subject: Avoid using the protect-tool to import pkcs#12. --- agent/ChangeLog | 26 + agent/Makefile.am | 3 +- agent/agent.h | 2 + agent/command.c | 278 +++++- agent/findkey.c | 6 +- agent/genkey.c | 199 ++-- agent/minip12.c | 2360 -------------------------------------------- agent/minip12.h | 36 - agent/preset-passphrase.c | 1 - agent/protect-tool.c | 19 +- agent/protect.c | 2 +- common/ChangeLog | 8 + common/membuf.c | 2 +- common/sexputil.c | 32 +- common/util.h | 2 + dirmngr/http.c | 2 + doc/dirmngr.texi | 251 +++++ g10/ChangeLog | 5 + g10/gpg.c | 4 - sm/ChangeLog | 16 + sm/Makefile.am | 1 + sm/call-agent.c | 137 ++- sm/gpgsm.h | 6 + sm/import.c | 542 +++++++---- sm/minip12.c | 2363 +++++++++++++++++++++++++++++++++++++++++++++ sm/minip12.h | 36 + 26 files changed, 3546 insertions(+), 2793 deletions(-) delete mode 100644 agent/minip12.c delete mode 100644 agent/minip12.h create mode 100644 sm/minip12.c create mode 100644 sm/minip12.h 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/minip12.c b/agent/minip12.c deleted file mode 100644 index 6f512e9e5..000000000 --- a/agent/minip12.c +++ /dev/null @@ -1,2360 +0,0 @@ -/* minip12.c - A minimal pkcs-12 implementation. - * Copyright (C) 2002, 2003, 2004, 2006 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 . - */ - -#ifdef HAVE_CONFIG_H -#include -#endif -#include -#include -#include -#include -#include -#include - -#ifdef TEST -#include -#include -#endif - -#include "../common/logging.h" -#include "../common/utf8conv.h" -#include "minip12.h" - -#ifndef DIM -#define DIM(v) (sizeof(v)/sizeof((v)[0])) -#endif - - -enum -{ - UNIVERSAL = 0, - APPLICATION = 1, - ASNCONTEXT = 2, - PRIVATE = 3 -}; - - -enum -{ - TAG_NONE = 0, - TAG_BOOLEAN = 1, - TAG_INTEGER = 2, - TAG_BIT_STRING = 3, - TAG_OCTET_STRING = 4, - TAG_NULL = 5, - TAG_OBJECT_ID = 6, - TAG_OBJECT_DESCRIPTOR = 7, - TAG_EXTERNAL = 8, - TAG_REAL = 9, - TAG_ENUMERATED = 10, - TAG_EMBEDDED_PDV = 11, - TAG_UTF8_STRING = 12, - TAG_REALTIVE_OID = 13, - TAG_SEQUENCE = 16, - TAG_SET = 17, - TAG_NUMERIC_STRING = 18, - TAG_PRINTABLE_STRING = 19, - TAG_TELETEX_STRING = 20, - TAG_VIDEOTEX_STRING = 21, - TAG_IA5_STRING = 22, - TAG_UTC_TIME = 23, - TAG_GENERALIZED_TIME = 24, - TAG_GRAPHIC_STRING = 25, - TAG_VISIBLE_STRING = 26, - TAG_GENERAL_STRING = 27, - TAG_UNIVERSAL_STRING = 28, - TAG_CHARACTER_STRING = 29, - TAG_BMP_STRING = 30 -}; - - -static unsigned char const oid_data[9] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 }; -static unsigned char const oid_encryptedData[9] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 }; -static unsigned char const oid_pkcs_12_keyBag[11] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 }; -static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 }; -static unsigned char const oid_pkcs_12_CertBag[11] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 }; -static unsigned char const oid_pkcs_12_CrlBag[11] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 }; - -static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 }; -static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 }; -static unsigned char const oid_x509Certificate_for_pkcs_12[10] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 }; - - -static unsigned char const oid_rsaEncryption[9] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; - - -static unsigned char const data_3desiter2048[30] = { - 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, - 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E, - 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; -#define DATA_3DESITER2048_SALT_OFF 18 - -static unsigned char const data_rc2iter2048[30] = { - 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, - 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E, - 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; -#define DATA_RC2ITER2048_SALT_OFF 18 - -static unsigned char const data_mactemplate[51] = { - 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, - 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, - 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, - 0x02, 0x08, 0x00 }; -#define DATA_MACTEMPLATE_MAC_OFF 17 -#define DATA_MACTEMPLATE_SALT_OFF 39 - -static unsigned char const data_attrtemplate[106] = { - 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31, - 0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00, - 0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00, - 0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00, - 0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, - 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, - 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, - 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, - 0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, - 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, - 0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, - 0x04, 0x14 }; /* Need to append SHA-1 digest. */ -#define DATA_ATTRTEMPLATE_KEYID_OFF 73 - -struct buffer_s -{ - unsigned char *buffer; - size_t length; -}; - - -struct tag_info -{ - int class; - int is_constructed; - unsigned long tag; - unsigned long length; /* length part of the TLV */ - int nhdr; - int ndef; /* It is an indefinite length */ -}; - - -/* Parse the buffer at the address BUFFER which is of SIZE and return - the tag and the length part from the TLV triplet. Update BUFFER - and SIZE on success. Checks that the encoded length does not - exhaust the length of the provided buffer. */ -static int -parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) -{ - int c; - unsigned long tag; - const unsigned char *buf = *buffer; - size_t length = *size; - - ti->length = 0; - ti->ndef = 0; - ti->nhdr = 0; - - /* Get the tag */ - if (!length) - return -1; /* premature eof */ - c = *buf++; length--; - ti->nhdr++; - - ti->class = (c & 0xc0) >> 6; - ti->is_constructed = !!(c & 0x20); - tag = c & 0x1f; - - if (tag == 0x1f) - { - tag = 0; - do - { - tag <<= 7; - if (!length) - return -1; /* premature eof */ - c = *buf++; length--; - ti->nhdr++; - tag |= c & 0x7f; - } - while (c & 0x80); - } - ti->tag = tag; - - /* Get the length */ - if (!length) - return -1; /* prematureeof */ - c = *buf++; length--; - ti->nhdr++; - - if ( !(c & 0x80) ) - ti->length = c; - else if (c == 0x80) - ti->ndef = 1; - else if (c == 0xff) - return -1; /* forbidden length value */ - else - { - unsigned long len = 0; - int count = c & 0x7f; - - for (; count; count--) - { - len <<= 8; - if (!length) - return -1; /* premature_eof */ - c = *buf++; length--; - ti->nhdr++; - len |= c & 0xff; - } - ti->length = len; - } - - if (ti->class == UNIVERSAL && !ti->tag) - ti->length = 0; - - if (ti->length > length) - return -1; /* data larger than buffer. */ - - *buffer = buf; - *size = length; - return 0; -} - - -/* Given an ASN.1 chunk of a structure like: - - 24 NDEF: OCTET STRING -- This is not passed to us - 04 1: OCTET STRING -- INPUT point s to here - : 30 - 04 1: OCTET STRING - : 80 - [...] - 04 2: OCTET STRING - : 00 00 - : } -- This denotes a Null tag and are the last - -- two bytes in INPUT. - - Create a new buffer with the content of that octet string. INPUT - is the orginal buffer with a length as stored at LENGTH. Returns - NULL on error or a new malloced buffer with the length of this new - buffer stored at LENGTH and the number of bytes parsed from input - are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is - allowed to be passed as NULL if the caller is not interested in - this value. */ -static unsigned char * -cram_octet_string (const unsigned char *input, size_t *length, - size_t *input_consumed) -{ - const unsigned char *s = input; - size_t n = *length; - unsigned char *output, *d; - struct tag_info ti; - - /* Allocate output buf. We know that it won't be longer than the - input buffer. */ - d = output = gcry_malloc (n); - if (!output) - goto bailout; - - for (;;) - { - if (parse_tag (&s, &n, &ti)) - goto bailout; - if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING - && !ti.ndef && !ti.is_constructed) - { - memcpy (d, s, ti.length); - s += ti.length; - d += ti.length; - n -= ti.length; - } - else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) - break; /* Ready */ - else - goto bailout; - } - - - *length = d - output; - if (input_consumed) - *input_consumed += s - input; - return output; - - bailout: - if (input_consumed) - *input_consumed += s - input; - gcry_free (output); - return NULL; -} - - - -static int -string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, - int req_keylen, unsigned char *keybuf) -{ - int rc, i, j; - gcry_md_hd_t md; - gcry_mpi_t num_b1 = NULL; - int pwlen; - unsigned char hash[20], buf_b[64], buf_i[128], *p; - size_t cur_keylen; - size_t n; - - cur_keylen = 0; - pwlen = strlen (pw); - if (pwlen > 63/2) - { - log_error ("password too long\n"); - return -1; - } - - if (saltlen < 8) - { - log_error ("salt too short\n"); - return -1; - } - - /* Store salt and password in BUF_I */ - p = buf_i; - for(i=0; i < 64; i++) - *p++ = salt [i%saltlen]; - for(i=j=0; i < 64; i += 2) - { - *p++ = 0; - *p++ = pw[j]; - if (++j > pwlen) /* Note, that we include the trailing zero */ - j = 0; - } - - for (;;) - { - rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); - if (rc) - { - log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc)); - return rc; - } - for(i=0; i < 64; i++) - gcry_md_putc (md, id); - gcry_md_write (md, buf_i, 128); - memcpy (hash, gcry_md_read (md, 0), 20); - gcry_md_close (md); - for (i=1; i < iter; i++) - gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20); - - for (i=0; i < 20 && cur_keylen < req_keylen; i++) - keybuf[cur_keylen++] = hash[i]; - if (cur_keylen == req_keylen) - { - gcry_mpi_release (num_b1); - return 0; /* ready */ - } - - /* need more bytes. */ - for(i=0; i < 64; i++) - buf_b[i] = hash[i % 20]; - rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n); - if (rc) - { - log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); - return -1; - } - gcry_mpi_add_ui (num_b1, num_b1, 1); - for (i=0; i < 128; i += 64) - { - gcry_mpi_t num_ij; - - rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n); - if (rc) - { - log_error ( "gcry_mpi_scan failed: %s\n", - gpg_strerror (rc)); - return -1; - } - gcry_mpi_add (num_ij, num_ij, num_b1); - gcry_mpi_clear_highbit (num_ij, 64*8); - rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij); - if (rc) - { - log_error ( "gcry_mpi_print failed: %s\n", - gpg_strerror (rc)); - return -1; - } - gcry_mpi_release (num_ij); - } - } -} - - -static int -set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, - const char *pw, int keybytes) -{ - unsigned char keybuf[24]; - int rc; - - assert (keybytes == 5 || keybytes == 24); - if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf)) - return -1; - rc = gcry_cipher_setkey (chd, keybuf, keybytes); - if (rc) - { - log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); - return -1; - } - - if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf)) - return -1; - rc = gcry_cipher_setiv (chd, keybuf, 8); - if (rc) - { - log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); - return -1; - } - return 0; -} - - -static void -crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, - int iter, const char *pw, int cipher_algo, int encrypt) -{ - gcry_cipher_hd_t chd; - int rc; - - rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0); - if (rc) - { - log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc)); - wipememory (buffer, length); - return; - } - if (set_key_iv (chd, salt, saltlen, iter, pw, - cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) - { - wipememory (buffer, length); - goto leave; - } - - rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0) - : gcry_cipher_decrypt (chd, buffer, length, NULL, 0); - - if (rc) - { - wipememory (buffer, length); - log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc)); - goto leave; - } - - leave: - gcry_cipher_close (chd); -} - - -/* Decrypt a block of data and try several encodings of the key. - CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is - a buffer of the same size to receive the decryption result. SALT, - SALTLEN, ITER and PW are the information required for decryption - and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a - function called with the plaintext and used to check whether the - decryption succeeded; i.e. that a correct passphrase has been - given. That function shall return true if the decryption has likely - succeeded. */ -static void -decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, - char *salt, size_t saltlen, - int iter, const char *pw, int cipher_algo, - int (*check_fnc) (const void *, size_t)) -{ - static const char * const charsets[] = { - "", /* No conversion - use the UTF-8 passphrase direct. */ - "ISO-8859-1", - "ISO-8859-15", - "ISO-8859-2", - "ISO-8859-3", - "ISO-8859-4", - "ISO-8859-5", - "ISO-8859-6", - "ISO-8859-7", - "ISO-8859-8", - "ISO-8859-9", - "KOI8-R", - "IBM437", - "IBM850", - "EUC-JP", - "BIG5", - NULL - }; - int charsetidx = 0; - char *convertedpw = NULL; /* Malloced and converted password or NULL. */ - size_t convertedpwsize = 0; /* Allocated length. */ - - for (charsetidx=0; charsets[charsetidx]; charsetidx++) - { - if (*charsets[charsetidx]) - { - jnlib_iconv_t cd; - const char *inptr; - char *outptr; - size_t inbytes, outbytes; - - if (!convertedpw) - { - /* We assume one byte encodings. Thus we can allocate - the buffer of the same size as the original - passphrase; the result will actually be shorter - then. */ - convertedpwsize = strlen (pw) + 1; - convertedpw = gcry_malloc_secure (convertedpwsize); - if (!convertedpw) - { - log_info ("out of secure memory while" - " converting passphrase\n"); - break; /* Give up. */ - } - } - - cd = jnlib_iconv_open (charsets[charsetidx], "utf-8"); - if (cd == (jnlib_iconv_t)(-1)) - continue; - - inptr = pw; - inbytes = strlen (pw); - outptr = convertedpw; - outbytes = convertedpwsize - 1; - if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, - &outptr, &outbytes) == (size_t)-1) - { - jnlib_iconv_close (cd); - continue; - } - *outptr = 0; - jnlib_iconv_close (cd); - log_info ("decryption failed; trying charset `%s'\n", - charsets[charsetidx]); - } - memcpy (plaintext, ciphertext, length); - crypt_block (plaintext, length, salt, saltlen, iter, - convertedpw? convertedpw:pw, cipher_algo, 0); - if (check_fnc (plaintext, length)) - break; /* Decryption succeeded. */ - } - gcry_free (convertedpw); -} - - -/* Return true if the decryption of an bag_encrypted_data object has - likely succeeded. */ -static int -bag_decrypted_data_p (const void *plaintext, size_t length) -{ - struct tag_info ti; - const unsigned char *p = plaintext; - size_t n = length; - - /* { */ - /* # warning debug code is enabled */ - /* FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */ - /* if (!fp || fwrite (p, n, 1, fp) != 1) */ - /* exit (2); */ - /* fclose (fp); */ - /* } */ - - if (parse_tag (&p, &n, &ti)) - return 0; - if (ti.class || ti.tag != TAG_SEQUENCE) - return 0; - if (parse_tag (&p, &n, &ti)) - return 0; - - return 1; -} - -/* Note: If R_RESULT is passed as NULL, a key object as already be - processed and thus we need to skip it here. */ -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) -{ - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; - const char *where; - char salt[20]; - size_t saltlen; - unsigned int iter; - unsigned char *plain = NULL; - int bad_pass = 0; - unsigned char *cram_buffer = NULL; - size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ - int is_3des = 0; - gcry_mpi_t *result = NULL; - int result_count; - - if (r_result) - *r_result = NULL; - where = "start"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_SEQUENCE) - goto bailout; - - where = "bag.encryptedData.version"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0) - goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_SEQUENCE) - goto bailout; - - where = "bag.encryptedData.data"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) - || memcmp (p, oid_data, DIM(oid_data))) - goto bailout; - p += DIM(oid_data); - n -= DIM(oid_data); - - where = "bag.encryptedData.keyinfo"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) - && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC, - DIM(oid_pbeWithSHAAnd40BitRC2_CBC))) - { - p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC); - n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC); - } - else if (!ti.class && ti.tag == TAG_OBJECT_ID - && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) - && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, - DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) - { - p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - is_3des = 1; - } - else - goto bailout; - - where = "rc2or3des-params"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING - || ti.length < 8 || ti.length > 20 ) - goto bailout; - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) - goto bailout; - for (iter=0; ti.length; ti.length--) - { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; - } - - where = "rc2or3des-ciphertext"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - - consumed = p - p_start; - if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "cram-rc2or3des-ciphertext"; - cram_buffer = cram_octet_string ( p, &n, &consumed); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - if (r_consumed) - *r_consumed = consumed; - r_consumed = NULL; /* Ugly hack to not update that value any further. */ - ti.length = n; - } - else if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.length ) - ; - else - goto bailout; - - log_info ("%lu bytes of %s encrypted text\n",ti.length,is_3des?"3DES":"RC2"); - - plain = gcry_malloc_secure (ti.length); - if (!plain) - { - log_error ("error allocating decryption buffer\n"); - goto bailout; - } - decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, - is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, - bag_decrypted_data_p); - n = ti.length; - startoffset = 0; - p_start = p = plain; - - where = "outer.outer.seq"; - if (parse_tag (&p, &n, &ti)) - { - bad_pass = 1; - goto bailout; - } - if (ti.class || ti.tag != TAG_SEQUENCE) - { - bad_pass = 1; - goto bailout; - } - - if (parse_tag (&p, &n, &ti)) - { - bad_pass = 1; - goto bailout; - } - - /* Loop over all certificates inside the bag. */ - while (n) - { - int iscrlbag = 0; - int iskeybag = 0; - - where = "certbag.nextcert"; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - - where = "certbag.objectidentifier"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID) - goto bailout; - if ( ti.length == DIM(oid_pkcs_12_CertBag) - && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) - { - p += DIM(oid_pkcs_12_CertBag); - n -= DIM(oid_pkcs_12_CertBag); - } - else if ( ti.length == DIM(oid_pkcs_12_CrlBag) - && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) - { - p += DIM(oid_pkcs_12_CrlBag); - n -= DIM(oid_pkcs_12_CrlBag); - iscrlbag = 1; - } - else if ( ti.length == DIM(oid_pkcs_12_keyBag) - && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag))) - { - /* The TrustedMIME plugin for MS Outlook started to create - files with just one outer 3DES encrypted container and - inside the certificates as well as the key. */ - p += DIM(oid_pkcs_12_keyBag); - n -= DIM(oid_pkcs_12_keyBag); - iskeybag = 1; - } - else - goto bailout; - - where = "certbag.before.certheader"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (iscrlbag) - { - log_info ("skipping unsupported crlBag\n"); - p += ti.length; - n -= ti.length; - } - else if (iskeybag && (result || !r_result)) - { - log_info ("one keyBag already processed; skipping this one\n"); - p += ti.length; - n -= ti.length; - } - else if (iskeybag) - { - int len; - - log_info ("processing simple keyBag\n"); - - /* Fixme: This code is duplicated from parse_bag_data. */ - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER - || ti.length != 1 || *p) - goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - len = ti.length; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_rsaEncryption) - || memcmp (p, oid_rsaEncryption, - DIM(oid_rsaEncryption))) - goto bailout; - p += DIM (oid_rsaEncryption); - n -= DIM (oid_rsaEncryption); - if (len < ti.length) - goto bailout; - len -= ti.length; - if (n < len) - goto bailout; - p += len; - n -= len; - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_OCTET_STRING) - goto bailout; - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - len = ti.length; - - result = gcry_calloc (10, sizeof *result); - if (!result) - { - log_error ( "error allocating result array\n"); - goto bailout; - } - result_count = 0; - - where = "reading.keybag.key-parameters"; - for (result_count = 0; len && result_count < 9;) - { - if ( parse_tag (&p, &n, &ti) - || ti.class || ti.tag != TAG_INTEGER) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (len < ti.length) - goto bailout; - len -= ti.length; - if (!result_count && ti.length == 1 && !*p) - ; /* ignore the very first one if it is a 0 */ - else - { - int rc; - - rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, - ti.length, NULL); - if (rc) - { - log_error ("error parsing key parameter: %s\n", - gpg_strerror (rc)); - goto bailout; - } - result_count++; - } - p += ti.length; - n -= ti.length; - } - if (len) - goto bailout; - } - else - { - log_info ("processing certBag\n"); - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_x509Certificate_for_pkcs_12) - || memcmp (p, oid_x509Certificate_for_pkcs_12, - DIM(oid_x509Certificate_for_pkcs_12))) - goto bailout; - p += DIM(oid_x509Certificate_for_pkcs_12); - n -= DIM(oid_x509Certificate_for_pkcs_12); - - where = "certbag.before.octetstring"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) - goto bailout; - - /* Return the certificate. */ - if (certcb) - certcb (certcbarg, p, ti.length); - - p += ti.length; - n -= ti.length; - } - - /* Ugly hack to cope with the padding: Forget about the rest if - that is less or equal to the cipher's block length. We can - reasonable assume that all valid data will be longer than - just one block. */ - if (n <= 8) - n = 0; - - /* Skip the optional SET with the pkcs12 cert attributes. */ - if (n) - { - where = "bag.attributes"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (!ti.class && ti.tag == TAG_SEQUENCE) - ; /* No attributes. */ - else if (!ti.class && ti.tag == TAG_SET && !ti.ndef) - { /* The optional SET. */ - p += ti.length; - n -= ti.length; - if (n <= 8) - n = 0; - if (n && parse_tag (&p, &n, &ti)) - goto bailout; - } - else - goto bailout; - } - } - - if (r_consumed) - *r_consumed = consumed; - gcry_free (plain); - gcry_free (cram_buffer); - if (r_result) - *r_result = result; - return 0; - - bailout: - if (result) - { - int i; - - for (i=0; result[i]; i++) - gcry_mpi_release (result[i]); - gcry_free (result); - } - if (r_consumed) - *r_consumed = consumed; - gcry_free (plain); - gcry_free (cram_buffer); - log_error ("encryptedData error at \"%s\", offset %u\n", - where, (unsigned int)((p - p_start)+startoffset)); - if (bad_pass) - { - /* Note, that the following string might be used by other programs - to check for a bad passphrase; it should therefore not be - translated or changed. */ - log_error ("possibly bad passphrase given\n"); - } - return -1; -} - - -/* Return true if the decryption of a bag_data object has likely - succeeded. */ -static int -bag_data_p (const void *plaintext, size_t length) -{ - struct tag_info ti; - const unsigned char *p = plaintext; - size_t n = length; - -/* { */ -/* # warning debug code is enabled */ -/* FILE *fp = fopen ("tmp-3des-plain-key.der", "wb"); */ -/* if (!fp || fwrite (p, n, 1, fp) != 1) */ -/* exit (2); */ -/* fclose (fp); */ -/* } */ - - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - return 0; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER - || ti.length != 1 || *p) - return 0; - - return 1; -} - - -static gcry_mpi_t * -parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, - size_t *r_consumed, const char *pw) -{ - int rc; - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; - const char *where; - char salt[20]; - size_t saltlen; - unsigned int iter; - int len; - unsigned char *plain = NULL; - gcry_mpi_t *result = NULL; - int result_count, i; - unsigned char *cram_buffer = NULL; - size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ - - where = "start"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING) - goto bailout; - - consumed = p - p_start; - if (ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "cram-data.outersegs"; - cram_buffer = cram_octet_string ( p, &n, &consumed); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - if (r_consumed) - *r_consumed = consumed; - r_consumed = NULL; /* Ugly hack to not update that value any further. */ - } - - - where = "data.outerseqs"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - - where = "data.objectidentifier"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) - || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, - DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) - goto bailout; - p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); - n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); - - where = "shrouded,outerseqs"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) - || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, - DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) - goto bailout; - p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); - - where = "3des-params"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING - || ti.length < 8 || ti.length > 20) - goto bailout; - saltlen = ti.length; - memcpy (salt, p, saltlen); - p += saltlen; - n -= saltlen; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) - goto bailout; - for (iter=0; ti.length; ti.length--) - { - iter <<= 8; - iter |= (*p++) & 0xff; - n--; - } - - where = "3des-ciphertext"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) - goto bailout; - - log_info ("%lu bytes of 3DES encrypted text\n", ti.length); - - plain = gcry_malloc_secure (ti.length); - if (!plain) - { - log_error ("error allocating decryption buffer\n"); - goto bailout; - } - consumed += p - p_start + ti.length; - decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, - GCRY_CIPHER_3DES, - bag_data_p); - n = ti.length; - startoffset = 0; - p_start = p = plain; - - where = "decrypted-text"; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER - || ti.length != 1 || *p) - goto bailout; - p++; n--; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - len = ti.length; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (ti.class || ti.tag != TAG_OBJECT_ID - || ti.length != DIM(oid_rsaEncryption) - || memcmp (p, oid_rsaEncryption, - DIM(oid_rsaEncryption))) - goto bailout; - p += DIM (oid_rsaEncryption); - n -= DIM (oid_rsaEncryption); - if (len < ti.length) - goto bailout; - len -= ti.length; - if (n < len) - goto bailout; - p += len; - n -= len; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) - goto bailout; - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) - goto bailout; - len = ti.length; - - result = gcry_calloc (10, sizeof *result); - if (!result) - { - log_error ( "error allocating result array\n"); - goto bailout; - } - result_count = 0; - - where = "reading.key-parameters"; - for (result_count=0; len && result_count < 9;) - { - if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) - goto bailout; - if (len < ti.nhdr) - goto bailout; - len -= ti.nhdr; - if (len < ti.length) - goto bailout; - len -= ti.length; - if (!result_count && ti.length == 1 && !*p) - ; /* ignore the very first one if it is a 0 */ - else - { - rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, - ti.length, NULL); - if (rc) - { - log_error ("error parsing key parameter: %s\n", - gpg_strerror (rc)); - goto bailout; - } - result_count++; - } - p += ti.length; - n -= ti.length; - } - if (len) - goto bailout; - - gcry_free (cram_buffer); - if (r_consumed) - *r_consumed = consumed; - return result; - - bailout: - gcry_free (plain); - if (result) - { - for (i=0; result[i]; i++) - gcry_mpi_release (result[i]); - gcry_free (result); - } - gcry_free (cram_buffer); - log_error ( "data error at \"%s\", offset %u\n", - where, (unsigned int)((p - buffer) + startoffset)); - if (r_consumed) - *r_consumed = consumed; - return NULL; -} - - -/* Parse a PKCS12 object and return an array of MPI representing the - secret key parameters. This is a very limited implementation in - that it is only able to look for 3DES encoded encryptedData and - tries to extract the first private key object it finds. In case of - an error NULL is returned. CERTCB and CERRTCBARG are used to pass - X.509 certificates back to the caller. */ -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) -{ - struct tag_info ti; - const unsigned char *p = buffer; - const unsigned char *p_start = buffer; - size_t n = length; - const char *where; - int bagseqlength, len; - int bagseqndef, lenndef; - gcry_mpi_t *result = NULL; - unsigned char *cram_buffer = NULL; - - where = "pfx"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_SEQUENCE) - goto bailout; - - where = "pfxVersion"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) - goto bailout; - p++; n--; - - where = "authSave"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_SEQUENCE) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) - || memcmp (p, oid_data, DIM(oid_data))) - goto bailout; - p += DIM(oid_data); - n -= DIM(oid_data); - - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != ASNCONTEXT || ti.tag) - goto bailout; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING) - goto bailout; - - if (ti.is_constructed && ti.ndef) - { - /* Mozilla exported certs now come with single byte chunks of - octect strings. (Mozilla Firefox 1.0.4). Arghh. */ - where = "cram-bags"; - cram_buffer = cram_octet_string ( p, &n, NULL); - if (!cram_buffer) - goto bailout; - p = p_start = cram_buffer; - } - - where = "bags"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) - goto bailout; - bagseqndef = ti.ndef; - bagseqlength = ti.length; - while (bagseqlength || bagseqndef) - { -/* log_debug ( "at offset %u\n", (p - p_start)); */ - where = "bag-sequence"; - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) - break; /* Ready */ - if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) - goto bailout; - - if (!bagseqndef) - { - if (bagseqlength < ti.nhdr) - goto bailout; - bagseqlength -= ti.nhdr; - if (bagseqlength < ti.length) - goto bailout; - bagseqlength -= ti.length; - } - lenndef = ti.ndef; - len = ti.length; - - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (lenndef) - len = ti.nhdr; - else - len -= ti.nhdr; - - if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) - && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) - { - size_t consumed = 0; - - p += DIM(oid_encryptedData); - n -= DIM(oid_encryptedData); - if (!lenndef) - len -= DIM(oid_encryptedData); - where = "bag.encryptedData"; - if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, - certcb, certcbarg, - result? NULL : &result)) - goto bailout; - if (lenndef) - len += consumed; - } - else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) - && !memcmp (p, oid_data, DIM(oid_data))) - { - if (result) - { - log_info ("already got an key object, skipping this one\n"); - p += ti.length; - n -= ti.length; - } - else - { - size_t consumed = 0; - - p += DIM(oid_data); - n -= DIM(oid_data); - if (!lenndef) - len -= DIM(oid_data); - result = parse_bag_data (p, n, (p - p_start), &consumed, pw); - if (!result) - goto bailout; - if (lenndef) - len += consumed; - } - } - else - { - log_info ("unknown bag type - skipped\n"); - p += ti.length; - n -= ti.length; - } - - if (len < 0 || len > n) - goto bailout; - p += len; - n -= len; - if (lenndef) - { - /* Need to skip the Null Tag. */ - if (parse_tag (&p, &n, &ti)) - goto bailout; - if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)) - goto bailout; - } - } - - gcry_free (cram_buffer); - return result; - bailout: - log_error ("error at \"%s\", offset %u\n", - where, (unsigned int)(p - p_start)); - if (result) - { - int i; - - for (i=0; result[i]; i++) - gcry_mpi_release (result[i]); - gcry_free (result); - } - gcry_free (cram_buffer); - return NULL; -} - - - -static size_t -compute_tag_length (size_t n) -{ - int needed = 0; - - if (n < 128) - needed += 2; /* tag and one length byte */ - else if (n < 256) - needed += 3; /* tag, number of length bytes, 1 length byte */ - else if (n < 65536) - needed += 4; /* tag, number of length bytes, 2 length bytes */ - else - { - log_error ("object too larger to encode\n"); - return 0; - } - return needed; -} - -static unsigned char * -store_tag_length (unsigned char *p, int tag, size_t n) -{ - if (tag == TAG_SEQUENCE) - tag |= 0x20; /* constructed */ - - *p++ = tag; - if (n < 128) - *p++ = n; - else if (n < 256) - { - *p++ = 0x81; - *p++ = n; - } - else if (n < 65536) - { - *p++ = 0x82; - *p++ = n >> 8; - *p++ = n; - } - - return p; -} - - -/* Create the final PKCS-12 object from the sequences contained in - SEQLIST. PW is the password. That array is terminated with an NULL - object. */ -static unsigned char * -create_final (struct buffer_s *sequences, const char *pw, size_t *r_length) -{ - int i; - size_t needed = 0; - size_t len[8], n; - unsigned char *macstart; - size_t maclen; - unsigned char *result, *p; - size_t resultlen; - char salt[8]; - unsigned char keybuf[20]; - gcry_md_hd_t md; - int rc; - int with_mac = 1; - - - /* 9 steps to create the pkcs#12 Krampf. */ - - /* 8. The MAC. */ - /* We add this at step 0. */ - - /* 7. All the buffers. */ - for (i=0; sequences[i].buffer; i++) - needed += sequences[i].length; - - /* 6. This goes into a sequences. */ - len[6] = needed; - n = compute_tag_length (needed); - needed += n; - - /* 5. Encapsulate all in an octet string. */ - len[5] = needed; - n = compute_tag_length (needed); - needed += n; - - /* 4. And tag it with [0]. */ - len[4] = needed; - n = compute_tag_length (needed); - needed += n; - - /* 3. Prepend an data OID. */ - needed += 2 + DIM (oid_data); - - /* 2. Put all into a sequences. */ - len[2] = needed; - n = compute_tag_length (needed); - needed += n; - - /* 1. Prepend the version integer 3. */ - needed += 3; - - /* 0. And the final outer sequence. */ - if (with_mac) - needed += DIM (data_mactemplate); - len[0] = needed; - n = compute_tag_length (needed); - needed += n; - - /* Allocate a buffer. */ - result = gcry_malloc (needed); - if (!result) - { - log_error ("error allocating buffer\n"); - return NULL; - } - p = result; - - /* 0. Store the very outer sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[0]); - - /* 1. Store the version integer 3. */ - *p++ = TAG_INTEGER; - *p++ = 1; - *p++ = 3; - - /* 2. Store another sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[2]); - - /* 3. Store the data OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); - memcpy (p, oid_data, DIM (oid_data)); - p += DIM (oid_data); - - /* 4. Next comes a context tag. */ - p = store_tag_length (p, 0xa0, len[4]); - - /* 5. And an octet string. */ - p = store_tag_length (p, TAG_OCTET_STRING, len[5]); - - /* 6. And the inner sequence. */ - macstart = p; - p = store_tag_length (p, TAG_SEQUENCE, len[6]); - - /* 7. Append all the buffers. */ - for (i=0; sequences[i].buffer; i++) - { - memcpy (p, sequences[i].buffer, sequences[i].length); - p += sequences[i].length; - } - - if (with_mac) - { - /* Intermezzo to compute the MAC. */ - maclen = p - macstart; - gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); - if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf)) - { - gcry_free (result); - return NULL; - } - rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); - if (rc) - { - log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc)); - gcry_free (result); - return NULL; - } - rc = gcry_md_setkey (md, keybuf, 20); - if (rc) - { - log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc)); - gcry_md_close (md); - gcry_free (result); - return NULL; - } - gcry_md_write (md, macstart, maclen); - - /* 8. Append the MAC template and fix it up. */ - memcpy (p, data_mactemplate, DIM (data_mactemplate)); - memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8); - memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20); - p += DIM (data_mactemplate); - gcry_md_close (md); - } - - /* Ready. */ - resultlen = p - result; - if (needed != resultlen) - log_debug ("length mismatch: %lu, %lu\n", - (unsigned long)needed, (unsigned long)resultlen); - - *r_length = resultlen; - return result; -} - - -/* Build a DER encoded SEQUENCE with the key: - - SEQUENCE { - INTEGER 0 - SEQUENCE { - OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1) - NULL - } - OCTET STRING, encapsulates { - SEQUENCE { - INTEGER 0 - INTEGER - INTEGER - INTEGER - INTEGER - INTEGER - INTEGER - INTEGER - INTEGER - } - } - } -*/ - -static unsigned char * -build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) -{ - int rc, i; - size_t needed, n; - unsigned char *plain, *p; - size_t plainlen; - size_t outseqlen, oidseqlen, octstrlen, inseqlen; - - needed = 3; /* The version(?) integer of value 0. */ - for (i=0; kparms[i]; i++) - { - n = 0; - rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); - if (rc) - { - log_error ("error formatting parameter: %s\n", gpg_strerror (rc)); - return NULL; - } - needed += n; - n = compute_tag_length (n); - if (!n) - return NULL; - needed += n; - } - if (i != 8) - { - log_error ("invalid parameters for p12_build\n"); - return NULL; - } - /* Now this all goes into a sequence. */ - inseqlen = needed; - n = compute_tag_length (needed); - if (!n) - return NULL; - needed += n; - /* Encapsulate all into an octet string. */ - octstrlen = needed; - n = compute_tag_length (needed); - if (!n) - return NULL; - needed += n; - /* Prepend the object identifier sequence. */ - oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; - needed += 2 + oidseqlen; - /* The version number. */ - needed += 3; - /* And finally put the whole thing into a sequence. */ - outseqlen = needed; - n = compute_tag_length (needed); - if (!n) - return NULL; - needed += n; - - /* allocate 8 extra bytes for padding */ - plain = gcry_malloc_secure (needed+8); - if (!plain) - { - log_error ("error allocating encryption buffer\n"); - return NULL; - } - - /* And now fill the plaintext buffer. */ - p = plain; - p = store_tag_length (p, TAG_SEQUENCE, outseqlen); - /* Store version. */ - *p++ = TAG_INTEGER; - *p++ = 1; - *p++ = 0; - /* Store object identifier sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); - memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); - p += DIM (oid_rsaEncryption); - *p++ = TAG_NULL; - *p++ = 0; - /* Start with the octet string. */ - p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); - p = store_tag_length (p, TAG_SEQUENCE, inseqlen); - /* Store the key parameters. */ - *p++ = TAG_INTEGER; - *p++ = 1; - *p++ = 0; - for (i=0; kparms[i]; i++) - { - n = 0; - rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); - if (rc) - { - log_error ("oops: error formatting parameter: %s\n", - gpg_strerror (rc)); - gcry_free (plain); - return NULL; - } - p = store_tag_length (p, TAG_INTEGER, n); - - n = plain + needed - p; - rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]); - if (rc) - { - log_error ("oops: error storing parameter: %s\n", - gpg_strerror (rc)); - gcry_free (plain); - return NULL; - } - p += n; - } - - plainlen = p - plain; - assert (needed == plainlen); - /* Append some pad characters; we already allocated extra space. */ - n = 8 - plainlen % 8; - for (i=0; i < n; i++, plainlen++) - *p++ = n; - - *r_length = plainlen; - return plain; -} - - - -static unsigned char * -build_key_bag (unsigned char *buffer, size_t buflen, char *salt, - const unsigned char *sha1hash, const char *keyidstr, - size_t *r_length) -{ - size_t len[11], needed; - unsigned char *p, *keybag; - size_t keybaglen; - - /* Walk 11 steps down to collect the info: */ - - /* 10. The data goes into an octet string. */ - needed = compute_tag_length (buflen); - needed += buflen; - - /* 9. Prepend the algorithm identifier. */ - needed += DIM (data_3desiter2048); - - /* 8. Put a sequence around. */ - len[8] = needed; - needed += compute_tag_length (needed); - - /* 7. Prepend a [0] tag. */ - len[7] = needed; - needed += compute_tag_length (needed); - - /* 6b. The attributes which are appended at the end. */ - if (sha1hash) - needed += DIM (data_attrtemplate) + 20; - - /* 6. Prepend the shroudedKeyBag OID. */ - needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); - - /* 5+4. Put all into two sequences. */ - len[5] = needed; - needed += compute_tag_length ( needed); - len[4] = needed; - needed += compute_tag_length (needed); - - /* 3. This all goes into an octet string. */ - len[3] = needed; - needed += compute_tag_length (needed); - - /* 2. Prepend another [0] tag. */ - len[2] = needed; - needed += compute_tag_length (needed); - - /* 1. Prepend the data OID. */ - needed += 2 + DIM (oid_data); - - /* 0. Prepend another sequence. */ - len[0] = needed; - needed += compute_tag_length (needed); - - /* Now that we have all length information, allocate a buffer. */ - p = keybag = gcry_malloc (needed); - if (!keybag) - { - log_error ("error allocating buffer\n"); - return NULL; - } - - /* Walk 11 steps up to store the data. */ - - /* 0. Store the first sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[0]); - - /* 1. Store the data OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); - memcpy (p, oid_data, DIM (oid_data)); - p += DIM (oid_data); - - /* 2. Store a [0] tag. */ - p = store_tag_length (p, 0xa0, len[2]); - - /* 3. And an octet string. */ - p = store_tag_length (p, TAG_OCTET_STRING, len[3]); - - /* 4+5. Two sequences. */ - p = store_tag_length (p, TAG_SEQUENCE, len[4]); - p = store_tag_length (p, TAG_SEQUENCE, len[5]); - - /* 6. Store the shroudedKeyBag OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, - DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); - memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, - DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); - p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); - - /* 7. Store a [0] tag. */ - p = store_tag_length (p, 0xa0, len[7]); - - /* 8. Store a sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[8]); - - /* 9. Now for the pre-encoded algorithm identifier and the salt. */ - memcpy (p, data_3desiter2048, DIM (data_3desiter2048)); - memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8); - p += DIM (data_3desiter2048); - - /* 10. And the octet string with the encrypted data. */ - p = store_tag_length (p, TAG_OCTET_STRING, buflen); - memcpy (p, buffer, buflen); - p += buflen; - - /* Append the attributes whose length we calculated at step 2b. */ - if (sha1hash) - { - int i; - - memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); - for (i=0; i < 8; i++) - p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; - p += DIM (data_attrtemplate); - memcpy (p, sha1hash, 20); - p += 20; - } - - - keybaglen = p - keybag; - if (needed != keybaglen) - log_debug ("length mismatch: %lu, %lu\n", - (unsigned long)needed, (unsigned long)keybaglen); - - *r_length = keybaglen; - return keybag; -} - - -static unsigned char * -build_cert_bag (unsigned char *buffer, size_t buflen, char *salt, - size_t *r_length) -{ - size_t len[9], needed; - unsigned char *p, *certbag; - size_t certbaglen; - - /* Walk 9 steps down to collect the info: */ - - /* 8. The data goes into an octet string. */ - needed = compute_tag_length (buflen); - needed += buflen; - - /* 7. The algorithm identifier. */ - needed += DIM (data_rc2iter2048); - - /* 6. The data OID. */ - needed += 2 + DIM (oid_data); - - /* 5. A sequence. */ - len[5] = needed; - needed += compute_tag_length ( needed); - - /* 4. An integer. */ - needed += 3; - - /* 3. A sequence. */ - len[3] = needed; - needed += compute_tag_length (needed); - - /* 2. A [0] tag. */ - len[2] = needed; - needed += compute_tag_length (needed); - - /* 1. The encryptedData OID. */ - needed += 2 + DIM (oid_encryptedData); - - /* 0. The first sequence. */ - len[0] = needed; - needed += compute_tag_length (needed); - - /* Now that we have all length information, allocate a buffer. */ - p = certbag = gcry_malloc (needed); - if (!certbag) - { - log_error ("error allocating buffer\n"); - return NULL; - } - - /* Walk 9 steps up to store the data. */ - - /* 0. Store the first sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[0]); - - /* 1. Store the encryptedData OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData)); - memcpy (p, oid_encryptedData, DIM (oid_encryptedData)); - p += DIM (oid_encryptedData); - - /* 2. Store a [0] tag. */ - p = store_tag_length (p, 0xa0, len[2]); - - /* 3. Store a sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[3]); - - /* 4. Store the integer 0. */ - *p++ = TAG_INTEGER; - *p++ = 1; - *p++ = 0; - - /* 5. Store a sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[5]); - - /* 6. Store the data OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); - memcpy (p, oid_data, DIM (oid_data)); - p += DIM (oid_data); - - /* 7. Now for the pre-encoded algorithm identifier and the salt. */ - memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048)); - memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8); - p += DIM (data_rc2iter2048); - - /* 8. And finally the [0] tag with the encrypted data. */ - p = store_tag_length (p, 0x80, buflen); - memcpy (p, buffer, buflen); - p += buflen; - certbaglen = p - certbag; - - if (needed != certbaglen) - log_debug ("length mismatch: %lu, %lu\n", - (unsigned long)needed, (unsigned long)certbaglen); - - *r_length = certbaglen; - return certbag; -} - - -static unsigned char * -build_cert_sequence (unsigned char *buffer, size_t buflen, - const unsigned char *sha1hash, const char *keyidstr, - size_t *r_length) -{ - size_t len[8], needed, n; - unsigned char *p, *certseq; - size_t certseqlen; - int i; - - assert (strlen (keyidstr) == 8); - - /* Walk 8 steps down to collect the info: */ - - /* 7. The data goes into an octet string. */ - needed = compute_tag_length (buflen); - needed += buflen; - - /* 6. A [0] tag. */ - len[6] = needed; - needed += compute_tag_length (needed); - - /* 5. An OID. */ - needed += 2 + DIM (oid_x509Certificate_for_pkcs_12); - - /* 4. A sequence. */ - len[4] = needed; - needed += compute_tag_length (needed); - - /* 3. A [0] tag. */ - len[3] = needed; - needed += compute_tag_length (needed); - - /* 2b. The attributes which are appended at the end. */ - if (sha1hash) - needed += DIM (data_attrtemplate) + 20; - - /* 2. An OID. */ - needed += 2 + DIM (oid_pkcs_12_CertBag); - - /* 1. A sequence. */ - len[1] = needed; - needed += compute_tag_length (needed); - - /* 0. The first sequence. */ - len[0] = needed; - needed += compute_tag_length (needed); - - /* Now that we have all length information, allocate a buffer. */ - p = certseq = gcry_malloc (needed + 8 /*(for padding)*/); - if (!certseq) - { - log_error ("error allocating buffer\n"); - return NULL; - } - - /* Walk 8 steps up to store the data. */ - - /* 0. Store the first sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[0]); - - /* 1. Store the second sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[1]); - - /* 2. Store the pkcs12-cert-bag OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag)); - memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag)); - p += DIM (oid_pkcs_12_CertBag); - - /* 3. Store a [0] tag. */ - p = store_tag_length (p, 0xa0, len[3]); - - /* 4. Store a sequence. */ - p = store_tag_length (p, TAG_SEQUENCE, len[4]); - - /* 5. Store the x509Certificate OID. */ - p = store_tag_length (p, TAG_OBJECT_ID, - DIM (oid_x509Certificate_for_pkcs_12)); - memcpy (p, oid_x509Certificate_for_pkcs_12, - DIM (oid_x509Certificate_for_pkcs_12)); - p += DIM (oid_x509Certificate_for_pkcs_12); - - /* 6. Store a [0] tag. */ - p = store_tag_length (p, 0xa0, len[6]); - - /* 7. And the octet string with the actual certificate. */ - p = store_tag_length (p, TAG_OCTET_STRING, buflen); - memcpy (p, buffer, buflen); - p += buflen; - - /* Append the attributes whose length we calculated at step 2b. */ - if (sha1hash) - { - memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); - for (i=0; i < 8; i++) - p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; - p += DIM (data_attrtemplate); - memcpy (p, sha1hash, 20); - p += 20; - } - - certseqlen = p - certseq; - if (needed != certseqlen) - log_debug ("length mismatch: %lu, %lu\n", - (unsigned long)needed, (unsigned long)certseqlen); - - /* Append some pad characters; we already allocated extra space. */ - n = 8 - certseqlen % 8; - for (i=0; i < n; i++, certseqlen++) - *p++ = n; - - *r_length = certseqlen; - return certseq; -} - - -/* Expect the RSA key parameters in KPARMS and a password in PW. - Create a PKCS structure from it and return it as well as the length - in R_LENGTH; return NULL in case of an error. If CHARSET is not - NULL, re-encode PW to that character set. */ -unsigned char * -p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, - const char *pw, const char *charset, size_t *r_length) -{ - unsigned char *buffer = NULL; - size_t n, buflen; - char salt[8]; - struct buffer_s seqlist[3]; - int seqlistidx = 0; - unsigned char sha1hash[20]; - char keyidstr[8+1]; - char *pwbuf = NULL; - size_t pwbufsize = 0; - - n = buflen = 0; /* (avoid compiler warning). */ - memset (sha1hash, 0, 20); - *keyidstr = 0; - - if (charset && pw && *pw) - { - jnlib_iconv_t cd; - const char *inptr; - char *outptr; - size_t inbytes, outbytes; - - /* We assume that the converted passphrase is at max 2 times - longer than its utf-8 encoding. */ - pwbufsize = strlen (pw)*2 + 1; - pwbuf = gcry_malloc_secure (pwbufsize); - if (!pwbuf) - { - log_error ("out of secure memory while converting passphrase\n"); - goto failure; - } - - cd = jnlib_iconv_open (charset, "utf-8"); - if (cd == (jnlib_iconv_t)(-1)) - { - log_error ("can't convert passphrase to" - " requested charset `%s': %s\n", - charset, strerror (errno)); - gcry_free (pwbuf); - goto failure; - } - - inptr = pw; - inbytes = strlen (pw); - outptr = pwbuf; - outbytes = pwbufsize - 1; - if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, - &outptr, &outbytes) == (size_t)-1) - { - log_error ("error converting passphrase to" - " requested charset `%s': %s\n", - charset, strerror (errno)); - gcry_free (pwbuf); - jnlib_iconv_close (cd); - goto failure; - } - *outptr = 0; - jnlib_iconv_close (cd); - pw = pwbuf; - } - - - if (cert && certlen) - { - /* Calculate the hash value we need for the bag attributes. */ - gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen); - sprintf (keyidstr, "%02x%02x%02x%02x", - sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]); - - /* Encode the certificate. */ - buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr, - &buflen); - if (!buffer) - goto failure; - - /* Encrypt it. */ - gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); - crypt_block (buffer, buflen, salt, 8, 2048, pw, - GCRY_CIPHER_RFC2268_40, 1); - - /* Encode the encrypted stuff into a bag. */ - seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); - seqlist[seqlistidx].length = n; - gcry_free (buffer); - buffer = NULL; - if (!seqlist[seqlistidx].buffer) - goto failure; - seqlistidx++; - } - - - if (kparms) - { - /* Encode the key. */ - buffer = build_key_sequence (kparms, &buflen); - if (!buffer) - goto failure; - - /* Encrypt it. */ - gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); - crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1); - - /* Encode the encrypted stuff into a bag. */ - if (cert && certlen) - seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, - sha1hash, keyidstr, &n); - else - seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, - NULL, NULL, &n); - seqlist[seqlistidx].length = n; - gcry_free (buffer); - buffer = NULL; - if (!seqlist[seqlistidx].buffer) - goto failure; - seqlistidx++; - } - - seqlist[seqlistidx].buffer = NULL; - seqlist[seqlistidx].length = 0; - - buffer = create_final (seqlist, pw, &buflen); - - failure: - if (pwbuf) - { - wipememory (pwbuf, pwbufsize); - gcry_free (pwbuf); - } - for ( ; seqlistidx; seqlistidx--) - gcry_free (seqlist[seqlistidx].buffer); - - *r_length = buffer? buflen : 0; - return buffer; -} - - -#ifdef TEST - -static void -cert_cb (void *opaque, const unsigned char *cert, size_t certlen) -{ - printf ("got a certificate of %u bytes length\n", certlen); -} - -int -main (int argc, char **argv) -{ - FILE *fp; - struct stat st; - unsigned char *buf; - size_t buflen; - gcry_mpi_t *result; - - if (argc != 3) - { - fprintf (stderr, "usage: testp12 file passphrase\n"); - return 1; - } - - gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); - gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); - - fp = fopen (argv[1], "rb"); - if (!fp) - { - fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno)); - return 1; - } - - if (fstat (fileno(fp), &st)) - { - fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno)); - return 1; - } - - buflen = st.st_size; - buf = gcry_malloc (buflen+1); - if (!buf || fread (buf, buflen, 1, fp) != 1) - { - fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno)); - return 1; - } - fclose (fp); - - result = p12_parse (buf, buflen, argv[2], cert_cb, NULL); - if (result) - { - int i, rc; - unsigned char *tmpbuf; - - for (i=0; result[i]; i++) - { - rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, - NULL, result[i]); - if (rc) - printf ("%d: [error printing number: %s]\n", - i, gpg_strerror (rc)); - else - { - printf ("%d: %s\n", i, tmpbuf); - gcry_free (tmpbuf); - } - } - } - - return 0; - -} - -/* -Local Variables: -compile-command: "gcc -Wall -O0 -g -DTEST=1 -o minip12 minip12.c ../common/libcommon.a -L /usr/local/lib -lgcrypt -lgpg-error" -End: -*/ -#endif /* TEST */ diff --git a/agent/minip12.h b/agent/minip12.h deleted file mode 100644 index 998f82f6a..000000000 --- a/agent/minip12.h +++ /dev/null @@ -1,36 +0,0 @@ -/* minip12.h - Global definitions for the minimal pkcs-12 implementation. - * Copyright (C) 2002, 2003 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 . - */ - -#ifndef MINIP12_H -#define MINIP12_H - -#include - -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); - -unsigned char *p12_build (gcry_mpi_t *kparms, - unsigned char *cert, size_t certlen, - const char *pw, const char *charset, - size_t *r_length); - - -#endif /*MINIP12_H*/ 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; - - /* 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) - { - argv[i++] = "--passphrase"; - argv[i++] = opt.fixed_passphrase; - } - 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); + 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); + + /* Check that n == p * q. */ + gcry_mpi_mul (t, skey->p, skey->q); + if (gcry_mpi_cmp( t, skey->n) ) + { + log_error ("RSA oops: n != p * q\n"); + err++; + } + + /* 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; - - *retfp = NULL; + memset (&store_cert_parm, 0, sizeof store_cert_parm); + store_cert_parm.ctrl = ctrl; + store_cert_parm.stats = stats; - /* 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; + for (p12bufoff=18; + p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n'; + p12bufoff++) + ; + p12bufoff++; + if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n') + p12bufoff++; } + else + p12bufoff = 0; - err = popen_protect_tool (ctrl, pgmname, tmpfp, certfp, &fp, &pid); + + 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) { - pid = -1; - goto cleanup; + log_error ("error parsing or decrypting the PKCS#12 file\n"); + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; } - es_fclose (tmpfp); - tmpfp = NULL; - /* Read stderr of the protect tool. */ - pos = 0; - cont_line = 0; - while ((c=es_getc (fp)) != EOF) +/* 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) { - /* 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'); - } + log_error ("failed to created S-expression from key: %s\n", + gpg_strerror (err)); + goto leave; } - if (pos) + /* Compute the keygrip. */ + if (!gcry_pk_get_keygrip (s_key, grip)) { - buffer[pos] = 0; - if (cont_line) - log_printf ("%s\n", buffer); - else - log_info ("%s\n", buffer); + 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; - /* 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); + /* 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; + } - cleanup: - es_fclose (tmpfp); - es_fclose (fp); - if (pid != -1) + /* 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) { - if (!gnupg_wait_process (pgmname, pid, 0, NULL)) - child_err = 0; - gnupg_release_process (pid); + err = gpg_error_from_syserror (); + goto leave; } - if (!err) - err = child_err; + + 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) { - es_fclose (certfp); + stats->count++; + stats->secret_read++; + stats->secret_imported++; } - else - *retfp = certfp; + 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/sm/minip12.c b/sm/minip12.c new file mode 100644 index 000000000..f50fbd419 --- /dev/null +++ b/sm/minip12.c @@ -0,0 +1,2363 @@ +/* minip12.c - A minimal pkcs-12 implementation. + * Copyright (C) 2002, 2003, 2004, 2006 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 . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#ifdef TEST +#include +#include +#endif + +#include "../common/logging.h" +#include "../common/utf8conv.h" +#include "minip12.h" + +#ifndef DIM +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#endif + + +enum +{ + UNIVERSAL = 0, + APPLICATION = 1, + ASNCONTEXT = 2, + PRIVATE = 3 +}; + + +enum +{ + TAG_NONE = 0, + TAG_BOOLEAN = 1, + TAG_INTEGER = 2, + TAG_BIT_STRING = 3, + TAG_OCTET_STRING = 4, + TAG_NULL = 5, + TAG_OBJECT_ID = 6, + TAG_OBJECT_DESCRIPTOR = 7, + TAG_EXTERNAL = 8, + TAG_REAL = 9, + TAG_ENUMERATED = 10, + TAG_EMBEDDED_PDV = 11, + TAG_UTF8_STRING = 12, + TAG_REALTIVE_OID = 13, + TAG_SEQUENCE = 16, + TAG_SET = 17, + TAG_NUMERIC_STRING = 18, + TAG_PRINTABLE_STRING = 19, + TAG_TELETEX_STRING = 20, + TAG_VIDEOTEX_STRING = 21, + TAG_IA5_STRING = 22, + TAG_UTC_TIME = 23, + TAG_GENERALIZED_TIME = 24, + TAG_GRAPHIC_STRING = 25, + TAG_VISIBLE_STRING = 26, + TAG_GENERAL_STRING = 27, + TAG_UNIVERSAL_STRING = 28, + TAG_CHARACTER_STRING = 29, + TAG_BMP_STRING = 30 +}; + + +static unsigned char const oid_data[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 }; +static unsigned char const oid_encryptedData[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 }; +static unsigned char const oid_pkcs_12_keyBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 }; +static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 }; +static unsigned char const oid_pkcs_12_CertBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 }; +static unsigned char const oid_pkcs_12_CrlBag[11] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 }; + +static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 }; +static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 }; +static unsigned char const oid_x509Certificate_for_pkcs_12[10] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 }; + + +static unsigned char const oid_rsaEncryption[9] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }; + + +static unsigned char const data_3desiter2048[30] = { + 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, + 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E, + 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; +#define DATA_3DESITER2048_SALT_OFF 18 + +static unsigned char const data_rc2iter2048[30] = { + 0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, + 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E, + 0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 }; +#define DATA_RC2ITER2048_SALT_OFF 18 + +static unsigned char const data_mactemplate[51] = { + 0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, + 0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, + 0x02, 0x08, 0x00 }; +#define DATA_MACTEMPLATE_MAC_OFF 17 +#define DATA_MACTEMPLATE_SALT_OFF 39 + +static unsigned char const data_attrtemplate[106] = { + 0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31, + 0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00, + 0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00, + 0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00, + 0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, + 0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, + 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, + 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00, + 0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, + 0x04, 0x14 }; /* Need to append SHA-1 digest. */ +#define DATA_ATTRTEMPLATE_KEYID_OFF 73 + +struct buffer_s +{ + unsigned char *buffer; + size_t length; +}; + + +struct tag_info +{ + int class; + int is_constructed; + unsigned long tag; + unsigned long length; /* length part of the TLV */ + int nhdr; + int ndef; /* It is an indefinite length */ +}; + + +/* Parse the buffer at the address BUFFER which is of SIZE and return + the tag and the length part from the TLV triplet. Update BUFFER + and SIZE on success. Checks that the encoded length does not + exhaust the length of the provided buffer. */ +static int +parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + int c; + unsigned long tag; + const unsigned char *buf = *buffer; + size_t length = *size; + + ti->length = 0; + ti->ndef = 0; + ti->nhdr = 0; + + /* Get the tag */ + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + + ti->class = (c & 0xc0) >> 6; + ti->is_constructed = !!(c & 0x20); + tag = c & 0x1f; + + if (tag == 0x1f) + { + tag = 0; + do + { + tag <<= 7; + if (!length) + return -1; /* premature eof */ + c = *buf++; length--; + ti->nhdr++; + tag |= c & 0x7f; + } + while (c & 0x80); + } + ti->tag = tag; + + /* Get the length */ + if (!length) + return -1; /* prematureeof */ + c = *buf++; length--; + ti->nhdr++; + + if ( !(c & 0x80) ) + ti->length = c; + else if (c == 0x80) + ti->ndef = 1; + else if (c == 0xff) + return -1; /* forbidden length value */ + else + { + unsigned long len = 0; + int count = c & 0x7f; + + for (; count; count--) + { + len <<= 8; + if (!length) + return -1; /* premature_eof */ + c = *buf++; length--; + ti->nhdr++; + len |= c & 0xff; + } + ti->length = len; + } + + if (ti->class == UNIVERSAL && !ti->tag) + ti->length = 0; + + if (ti->length > length) + return -1; /* data larger than buffer. */ + + *buffer = buf; + *size = length; + return 0; +} + + +/* Given an ASN.1 chunk of a structure like: + + 24 NDEF: OCTET STRING -- This is not passed to us + 04 1: OCTET STRING -- INPUT point s to here + : 30 + 04 1: OCTET STRING + : 80 + [...] + 04 2: OCTET STRING + : 00 00 + : } -- This denotes a Null tag and are the last + -- two bytes in INPUT. + + Create a new buffer with the content of that octet string. INPUT + is the orginal buffer with a length as stored at LENGTH. Returns + NULL on error or a new malloced buffer with the length of this new + buffer stored at LENGTH and the number of bytes parsed from input + are added to the value stored at INPUT_CONSUMED. INPUT_CONSUMED is + allowed to be passed as NULL if the caller is not interested in + this value. */ +static unsigned char * +cram_octet_string (const unsigned char *input, size_t *length, + size_t *input_consumed) +{ + const unsigned char *s = input; + size_t n = *length; + unsigned char *output, *d; + struct tag_info ti; + + /* Allocate output buf. We know that it won't be longer than the + input buffer. */ + d = output = gcry_malloc (n); + if (!output) + goto bailout; + + for (;;) + { + if (parse_tag (&s, &n, &ti)) + goto bailout; + if (ti.class == UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + memcpy (d, s, ti.length); + s += ti.length; + d += ti.length; + n -= ti.length; + } + else if (ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + else + goto bailout; + } + + + *length = d - output; + if (input_consumed) + *input_consumed += s - input; + return output; + + bailout: + if (input_consumed) + *input_consumed += s - input; + gcry_free (output); + return NULL; +} + + + +static int +string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw, + int req_keylen, unsigned char *keybuf) +{ + int rc, i, j; + gcry_md_hd_t md; + gcry_mpi_t num_b1 = NULL; + int pwlen; + unsigned char hash[20], buf_b[64], buf_i[128], *p; + size_t cur_keylen; + size_t n; + + cur_keylen = 0; + pwlen = strlen (pw); + if (pwlen > 63/2) + { + log_error ("password too long\n"); + return -1; + } + + if (saltlen < 8) + { + log_error ("salt too short\n"); + return -1; + } + + /* Store salt and password in BUF_I */ + p = buf_i; + for(i=0; i < 64; i++) + *p++ = salt [i%saltlen]; + for(i=j=0; i < 64; i += 2) + { + *p++ = 0; + *p++ = pw[j]; + if (++j > pwlen) /* Note, that we include the trailing zero */ + j = 0; + } + + for (;;) + { + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + for(i=0; i < 64; i++) + gcry_md_putc (md, id); + gcry_md_write (md, buf_i, 128); + memcpy (hash, gcry_md_read (md, 0), 20); + gcry_md_close (md); + for (i=1; i < iter; i++) + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20); + + for (i=0; i < 20 && cur_keylen < req_keylen; i++) + keybuf[cur_keylen++] = hash[i]; + if (cur_keylen == req_keylen) + { + gcry_mpi_release (num_b1); + return 0; /* ready */ + } + + /* need more bytes. */ + for(i=0; i < 64; i++) + buf_b[i] = hash[i % 20]; + rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc)); + return -1; + } + gcry_mpi_add_ui (num_b1, num_b1, 1); + for (i=0; i < 128; i += 64) + { + gcry_mpi_t num_ij; + + rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n); + if (rc) + { + log_error ( "gcry_mpi_scan failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_add (num_ij, num_ij, num_b1); + gcry_mpi_clear_highbit (num_ij, 64*8); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij); + if (rc) + { + log_error ( "gcry_mpi_print failed: %s\n", + gpg_strerror (rc)); + return -1; + } + gcry_mpi_release (num_ij); + } + } +} + + +static int +set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter, + const char *pw, int keybytes) +{ + unsigned char keybuf[24]; + int rc; + + assert (keybytes == 5 || keybytes == 24); + if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf)) + return -1; + rc = gcry_cipher_setkey (chd, keybuf, keybytes); + if (rc) + { + log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc)); + return -1; + } + + if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf)) + return -1; + rc = gcry_cipher_setiv (chd, keybuf, 8); + if (rc) + { + log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc)); + return -1; + } + return 0; +} + + +static void +crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen, + int iter, const char *pw, int cipher_algo, int encrypt) +{ + gcry_cipher_hd_t chd; + int rc; + + rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0); + if (rc) + { + log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc)); + wipememory (buffer, length); + return; + } + if (set_key_iv (chd, salt, saltlen, iter, pw, + cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24)) + { + wipememory (buffer, length); + goto leave; + } + + rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0) + : gcry_cipher_decrypt (chd, buffer, length, NULL, 0); + + if (rc) + { + wipememory (buffer, length); + log_error ( "en/de-crytion failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + leave: + gcry_cipher_close (chd); +} + + +/* Decrypt a block of data and try several encodings of the key. + CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is + a buffer of the same size to receive the decryption result. SALT, + SALTLEN, ITER and PW are the information required for decryption + and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a + function called with the plaintext and used to check whether the + decryption succeeded; i.e. that a correct passphrase has been + given. That function shall return true if the decryption has likely + succeeded. */ +static void +decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length, + char *salt, size_t saltlen, + int iter, const char *pw, int cipher_algo, + int (*check_fnc) (const void *, size_t)) +{ + static const char * const charsets[] = { + "", /* No conversion - use the UTF-8 passphrase direct. */ + "ISO-8859-1", + "ISO-8859-15", + "ISO-8859-2", + "ISO-8859-3", + "ISO-8859-4", + "ISO-8859-5", + "ISO-8859-6", + "ISO-8859-7", + "ISO-8859-8", + "ISO-8859-9", + "KOI8-R", + "IBM437", + "IBM850", + "EUC-JP", + "BIG5", + NULL + }; + int charsetidx = 0; + char *convertedpw = NULL; /* Malloced and converted password or NULL. */ + size_t convertedpwsize = 0; /* Allocated length. */ + + for (charsetidx=0; charsets[charsetidx]; charsetidx++) + { + if (*charsets[charsetidx]) + { + jnlib_iconv_t cd; + const char *inptr; + char *outptr; + size_t inbytes, outbytes; + + if (!convertedpw) + { + /* We assume one byte encodings. Thus we can allocate + the buffer of the same size as the original + passphrase; the result will actually be shorter + then. */ + convertedpwsize = strlen (pw) + 1; + convertedpw = gcry_malloc_secure (convertedpwsize); + if (!convertedpw) + { + log_info ("out of secure memory while" + " converting passphrase\n"); + break; /* Give up. */ + } + } + + cd = jnlib_iconv_open (charsets[charsetidx], "utf-8"); + if (cd == (jnlib_iconv_t)(-1)) + continue; + + inptr = pw; + inbytes = strlen (pw); + outptr = convertedpw; + outbytes = convertedpwsize - 1; + if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, + &outptr, &outbytes) == (size_t)-1) + { + jnlib_iconv_close (cd); + continue; + } + *outptr = 0; + jnlib_iconv_close (cd); + log_info ("decryption failed; trying charset `%s'\n", + charsets[charsetidx]); + } + memcpy (plaintext, ciphertext, length); + crypt_block (plaintext, length, salt, saltlen, iter, + convertedpw? convertedpw:pw, cipher_algo, 0); + if (check_fnc (plaintext, length)) + break; /* Decryption succeeded. */ + } + gcry_free (convertedpw); +} + + +/* Return true if the decryption of an bag_encrypted_data object has + likely succeeded. */ +static int +bag_decrypted_data_p (const void *plaintext, size_t length) +{ + struct tag_info ti; + const unsigned char *p = plaintext; + size_t n = length; + + /* { */ + /* # warning debug code is enabled */ + /* FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */ + /* if (!fp || fwrite (p, n, 1, fp) != 1) */ + /* exit (2); */ + /* fclose (fp); */ + /* } */ + + if (parse_tag (&p, &n, &ti)) + return 0; + if (ti.class || ti.tag != TAG_SEQUENCE) + return 0; + if (parse_tag (&p, &n, &ti)) + return 0; + + return 1; +} + +/* Note: If R_RESULT is passed as NULL, a key object as already be + processed and thus we need to skip it here. */ +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, + int *r_badpass) +{ + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + char salt[20]; + size_t saltlen; + unsigned int iter; + unsigned char *plain = NULL; + int bad_pass = 0; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ + int is_3des = 0; + gcry_mpi_t *result = NULL; + int result_count; + + if (r_result) + *r_result = NULL; + where = "start"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.version"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 0) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "bag.encryptedData.data"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + where = "bag.encryptedData.keyinfo"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pbeWithSHAAnd40BitRC2_CBC) + && !memcmp (p, oid_pbeWithSHAAnd40BitRC2_CBC, + DIM(oid_pbeWithSHAAnd40BitRC2_CBC))) + { + p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC); + n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC); + } + else if (!ti.class && ti.tag == TAG_OBJECT_ID + && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + { + p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + is_3des = 1; + } + else + goto bailout; + + where = "rc2or3des-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 20 ) + goto bailout; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + goto bailout; + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + + where = "rc2or3des-ciphertext"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + + consumed = p - p_start; + if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-rc2or3des-ciphertext"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Ugly hack to not update that value any further. */ + ti.length = n; + } + else if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.length ) + ; + else + goto bailout; + + log_info ("%lu bytes of %s encrypted text\n",ti.length,is_3des?"3DES":"RC2"); + + plain = gcry_malloc_secure (ti.length); + if (!plain) + { + log_error ("error allocating decryption buffer\n"); + goto bailout; + } + decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, + is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, + bag_decrypted_data_p); + n = ti.length; + startoffset = 0; + p_start = p = plain; + + where = "outer.outer.seq"; + if (parse_tag (&p, &n, &ti)) + { + bad_pass = 1; + goto bailout; + } + if (ti.class || ti.tag != TAG_SEQUENCE) + { + bad_pass = 1; + goto bailout; + } + + if (parse_tag (&p, &n, &ti)) + { + bad_pass = 1; + goto bailout; + } + + /* Loop over all certificates inside the bag. */ + while (n) + { + int iscrlbag = 0; + int iskeybag = 0; + + where = "certbag.nextcert"; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "certbag.objectidentifier"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID) + goto bailout; + if ( ti.length == DIM(oid_pkcs_12_CertBag) + && !memcmp (p, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag))) + { + p += DIM(oid_pkcs_12_CertBag); + n -= DIM(oid_pkcs_12_CertBag); + } + else if ( ti.length == DIM(oid_pkcs_12_CrlBag) + && !memcmp (p, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag))) + { + p += DIM(oid_pkcs_12_CrlBag); + n -= DIM(oid_pkcs_12_CrlBag); + iscrlbag = 1; + } + else if ( ti.length == DIM(oid_pkcs_12_keyBag) + && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag))) + { + /* The TrustedMIME plugin for MS Outlook started to create + files with just one outer 3DES encrypted container and + inside the certificates as well as the key. */ + p += DIM(oid_pkcs_12_keyBag); + n -= DIM(oid_pkcs_12_keyBag); + iskeybag = 1; + } + else + goto bailout; + + where = "certbag.before.certheader"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (iscrlbag) + { + log_info ("skipping unsupported crlBag\n"); + p += ti.length; + n -= ti.length; + } + else if (iskeybag && (result || !r_result)) + { + log_info ("one keyBag already processed; skipping this one\n"); + p += ti.length; + n -= ti.length; + } + else if (iskeybag) + { + int len; + + log_info ("processing simple keyBag\n"); + + /* Fixme: This code is duplicated from parse_bag_data. */ + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_rsaEncryption) + || memcmp (p, oid_rsaEncryption, + DIM(oid_rsaEncryption))) + goto bailout; + p += DIM (oid_rsaEncryption); + n -= DIM (oid_rsaEncryption); + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + p += len; + n -= len; + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + + result = gcry_calloc (10, sizeof *result); + if (!result) + { + log_error ( "error allocating result array\n"); + goto bailout; + } + result_count = 0; + + where = "reading.keybag.key-parameters"; + for (result_count = 0; len && result_count < 9;) + { + if ( parse_tag (&p, &n, &ti) + || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (!result_count && ti.length == 1 && !*p) + ; /* ignore the very first one if it is a 0 */ + else + { + int rc; + + rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, + ti.length, NULL); + if (rc) + { + log_error ("error parsing key parameter: %s\n", + gpg_strerror (rc)); + goto bailout; + } + result_count++; + } + p += ti.length; + n -= ti.length; + } + if (len) + goto bailout; + } + else + { + log_info ("processing certBag\n"); + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_x509Certificate_for_pkcs_12) + || memcmp (p, oid_x509Certificate_for_pkcs_12, + DIM(oid_x509Certificate_for_pkcs_12))) + goto bailout; + p += DIM(oid_x509Certificate_for_pkcs_12); + n -= DIM(oid_x509Certificate_for_pkcs_12); + + where = "certbag.before.octetstring"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || ti.ndef) + goto bailout; + + /* Return the certificate. */ + if (certcb) + certcb (certcbarg, p, ti.length); + + p += ti.length; + n -= ti.length; + } + + /* Ugly hack to cope with the padding: Forget about the rest if + that is less or equal to the cipher's block length. We can + reasonable assume that all valid data will be longer than + just one block. */ + if (n <= 8) + n = 0; + + /* Skip the optional SET with the pkcs12 cert attributes. */ + if (n) + { + where = "bag.attributes"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!ti.class && ti.tag == TAG_SEQUENCE) + ; /* No attributes. */ + else if (!ti.class && ti.tag == TAG_SET && !ti.ndef) + { /* The optional SET. */ + p += ti.length; + n -= ti.length; + if (n <= 8) + n = 0; + if (n && parse_tag (&p, &n, &ti)) + goto bailout; + } + else + goto bailout; + } + } + + if (r_consumed) + *r_consumed = consumed; + gcry_free (plain); + gcry_free (cram_buffer); + if (r_result) + *r_result = result; + return 0; + + bailout: + if (result) + { + int i; + + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + if (r_consumed) + *r_consumed = consumed; + gcry_free (plain); + gcry_free (cram_buffer); + log_error ("encryptedData error at \"%s\", offset %u\n", + where, (unsigned int)((p - p_start)+startoffset)); + if (bad_pass) + { + /* Note, that the following string might be used by other programs + 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; +} + + +/* Return true if the decryption of a bag_data object has likely + succeeded. */ +static int +bag_data_p (const void *plaintext, size_t length) +{ + struct tag_info ti; + const unsigned char *p = plaintext; + size_t n = length; + +/* { */ +/* # warning debug code is enabled */ +/* FILE *fp = fopen ("tmp-3des-plain-key.der", "wb"); */ +/* if (!fp || fwrite (p, n, 1, fp) != 1) */ +/* exit (2); */ +/* fclose (fp); */ +/* } */ + + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + return 0; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + return 0; + + return 1; +} + + +static gcry_mpi_t * +parse_bag_data (const unsigned char *buffer, size_t length, int startoffset, + size_t *r_consumed, const char *pw) +{ + int rc; + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + char salt[20]; + size_t saltlen; + unsigned int iter; + int len; + unsigned char *plain = NULL; + gcry_mpi_t *result = NULL; + int result_count, i; + unsigned char *cram_buffer = NULL; + size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */ + + where = "start"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + + consumed = p - p_start; + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-data.outersegs"; + cram_buffer = cram_octet_string ( p, &n, &consumed); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + if (r_consumed) + *r_consumed = consumed; + r_consumed = NULL; /* Ugly hack to not update that value any further. */ + } + + + where = "data.outerseqs"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "data.objectidentifier"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag) + || memcmp (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag))) + goto bailout; + p += DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + n -= DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag); + + where = "shrouded,outerseqs"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC) + || memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC, + DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC))) + goto bailout; + p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC); + + where = "3des-params"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING + || ti.length < 8 || ti.length > 20) + goto bailout; + saltlen = ti.length; + memcpy (salt, p, saltlen); + p += saltlen; + n -= saltlen; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_INTEGER || !ti.length ) + goto bailout; + for (iter=0; ti.length; ti.length--) + { + iter <<= 8; + iter |= (*p++) & 0xff; + n--; + } + + where = "3des-ciphertext"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class || ti.tag != TAG_OCTET_STRING || !ti.length ) + goto bailout; + + log_info ("%lu bytes of 3DES encrypted text\n", ti.length); + + plain = gcry_malloc_secure (ti.length); + if (!plain) + { + log_error ("error allocating decryption buffer\n"); + goto bailout; + } + consumed += p - p_start + ti.length; + decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, + GCRY_CIPHER_3DES, + bag_data_p); + n = ti.length; + startoffset = 0; + p_start = p = plain; + + where = "decrypted-text"; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER + || ti.length != 1 || *p) + goto bailout; + p++; n--; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (ti.class || ti.tag != TAG_OBJECT_ID + || ti.length != DIM(oid_rsaEncryption) + || memcmp (p, oid_rsaEncryption, + DIM(oid_rsaEncryption))) + goto bailout; + p += DIM (oid_rsaEncryption); + n -= DIM (oid_rsaEncryption); + if (len < ti.length) + goto bailout; + len -= ti.length; + if (n < len) + goto bailout; + p += len; + n -= len; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_OCTET_STRING) + goto bailout; + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE) + goto bailout; + len = ti.length; + + result = gcry_calloc (10, sizeof *result); + if (!result) + { + log_error ( "error allocating result array\n"); + goto bailout; + } + result_count = 0; + + where = "reading.key-parameters"; + for (result_count=0; len && result_count < 9;) + { + if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER) + goto bailout; + if (len < ti.nhdr) + goto bailout; + len -= ti.nhdr; + if (len < ti.length) + goto bailout; + len -= ti.length; + if (!result_count && ti.length == 1 && !*p) + ; /* ignore the very first one if it is a 0 */ + else + { + rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p, + ti.length, NULL); + if (rc) + { + log_error ("error parsing key parameter: %s\n", + gpg_strerror (rc)); + goto bailout; + } + result_count++; + } + p += ti.length; + n -= ti.length; + } + if (len) + goto bailout; + + gcry_free (cram_buffer); + if (r_consumed) + *r_consumed = consumed; + return result; + + bailout: + gcry_free (plain); + if (result) + { + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + gcry_free (cram_buffer); + log_error ( "data error at \"%s\", offset %u\n", + where, (unsigned int)((p - buffer) + startoffset)); + if (r_consumed) + *r_consumed = consumed; + return NULL; +} + + +/* Parse a PKCS12 object and return an array of MPI representing the + secret key parameters. This is a very limited implementation in + that it is only able to look for 3DES encoded encryptedData and + tries to extract the first private key object it finds. In case of + an error NULL is returned. CERTCB and CERRTCBARG are used to pass + X.509 certificates back to the caller. */ +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, int *r_badpass) +{ + struct tag_info ti; + const unsigned char *p = buffer; + const unsigned char *p_start = buffer; + size_t n = length; + const char *where; + int bagseqlength, len; + int bagseqndef, lenndef; + gcry_mpi_t *result = NULL; + unsigned char *cram_buffer = NULL; + + *r_badpass = 0; + where = "pfx"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + + where = "pfxVersion"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_INTEGER || ti.length != 1 || *p != 3) + goto bailout; + p++; n--; + + where = "authSave"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_SEQUENCE) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.tag != TAG_OBJECT_ID || ti.length != DIM(oid_data) + || memcmp (p, oid_data, DIM(oid_data))) + goto bailout; + p += DIM(oid_data); + n -= DIM(oid_data); + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != ASNCONTEXT || ti.tag) + goto bailout; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != UNIVERSAL || ti.tag != TAG_OCTET_STRING) + goto bailout; + + if (ti.is_constructed && ti.ndef) + { + /* Mozilla exported certs now come with single byte chunks of + octect strings. (Mozilla Firefox 1.0.4). Arghh. */ + where = "cram-bags"; + cram_buffer = cram_octet_string ( p, &n, NULL); + if (!cram_buffer) + goto bailout; + p = p_start = cram_buffer; + } + + where = "bags"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + bagseqndef = ti.ndef; + bagseqlength = ti.length; + while (bagseqlength || bagseqndef) + { +/* log_debug ( "at offset %u\n", (p - p_start)); */ + where = "bag-sequence"; + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (bagseqndef && ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed) + break; /* Ready */ + if (ti.class != UNIVERSAL || ti.tag != TAG_SEQUENCE) + goto bailout; + + if (!bagseqndef) + { + if (bagseqlength < ti.nhdr) + goto bailout; + bagseqlength -= ti.nhdr; + if (bagseqlength < ti.length) + goto bailout; + bagseqlength -= ti.length; + } + lenndef = ti.ndef; + len = ti.length; + + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (lenndef) + len = ti.nhdr; + else + len -= ti.nhdr; + + if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_encryptedData) + && !memcmp (p, oid_encryptedData, DIM(oid_encryptedData))) + { + size_t consumed = 0; + + p += DIM(oid_encryptedData); + n -= DIM(oid_encryptedData); + if (!lenndef) + len -= DIM(oid_encryptedData); + where = "bag.encryptedData"; + if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw, + certcb, certcbarg, + result? NULL : &result, r_badpass)) + goto bailout; + if (lenndef) + len += consumed; + } + else if (ti.tag == TAG_OBJECT_ID && ti.length == DIM(oid_data) + && !memcmp (p, oid_data, DIM(oid_data))) + { + if (result) + { + log_info ("already got an key object, skipping this one\n"); + p += ti.length; + n -= ti.length; + } + else + { + size_t consumed = 0; + + p += DIM(oid_data); + n -= DIM(oid_data); + if (!lenndef) + len -= DIM(oid_data); + result = parse_bag_data (p, n, (p - p_start), &consumed, pw); + if (!result) + goto bailout; + if (lenndef) + len += consumed; + } + } + else + { + log_info ("unknown bag type - skipped\n"); + p += ti.length; + n -= ti.length; + } + + if (len < 0 || len > n) + goto bailout; + p += len; + n -= len; + if (lenndef) + { + /* Need to skip the Null Tag. */ + if (parse_tag (&p, &n, &ti)) + goto bailout; + if (!(ti.class == UNIVERSAL && !ti.tag && !ti.is_constructed)) + goto bailout; + } + } + + gcry_free (cram_buffer); + return result; + bailout: + log_error ("error at \"%s\", offset %u\n", + where, (unsigned int)(p - p_start)); + if (result) + { + int i; + + for (i=0; result[i]; i++) + gcry_mpi_release (result[i]); + gcry_free (result); + } + gcry_free (cram_buffer); + return NULL; +} + + + +static size_t +compute_tag_length (size_t n) +{ + int needed = 0; + + if (n < 128) + needed += 2; /* tag and one length byte */ + else if (n < 256) + needed += 3; /* tag, number of length bytes, 1 length byte */ + else if (n < 65536) + needed += 4; /* tag, number of length bytes, 2 length bytes */ + else + { + log_error ("object too larger to encode\n"); + return 0; + } + return needed; +} + +static unsigned char * +store_tag_length (unsigned char *p, int tag, size_t n) +{ + if (tag == TAG_SEQUENCE) + tag |= 0x20; /* constructed */ + + *p++ = tag; + if (n < 128) + *p++ = n; + else if (n < 256) + { + *p++ = 0x81; + *p++ = n; + } + else if (n < 65536) + { + *p++ = 0x82; + *p++ = n >> 8; + *p++ = n; + } + + return p; +} + + +/* Create the final PKCS-12 object from the sequences contained in + SEQLIST. PW is the password. That array is terminated with an NULL + object. */ +static unsigned char * +create_final (struct buffer_s *sequences, const char *pw, size_t *r_length) +{ + int i; + size_t needed = 0; + size_t len[8], n; + unsigned char *macstart; + size_t maclen; + unsigned char *result, *p; + size_t resultlen; + char salt[8]; + unsigned char keybuf[20]; + gcry_md_hd_t md; + int rc; + int with_mac = 1; + + + /* 9 steps to create the pkcs#12 Krampf. */ + + /* 8. The MAC. */ + /* We add this at step 0. */ + + /* 7. All the buffers. */ + for (i=0; sequences[i].buffer; i++) + needed += sequences[i].length; + + /* 6. This goes into a sequences. */ + len[6] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 5. Encapsulate all in an octet string. */ + len[5] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 4. And tag it with [0]. */ + len[4] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 3. Prepend an data OID. */ + needed += 2 + DIM (oid_data); + + /* 2. Put all into a sequences. */ + len[2] = needed; + n = compute_tag_length (needed); + needed += n; + + /* 1. Prepend the version integer 3. */ + needed += 3; + + /* 0. And the final outer sequence. */ + if (with_mac) + needed += DIM (data_mactemplate); + len[0] = needed; + n = compute_tag_length (needed); + needed += n; + + /* Allocate a buffer. */ + result = gcry_malloc (needed); + if (!result) + { + log_error ("error allocating buffer\n"); + return NULL; + } + p = result; + + /* 0. Store the very outer sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the version integer 3. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 3; + + /* 2. Store another sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[2]); + + /* 3. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 4. Next comes a context tag. */ + p = store_tag_length (p, 0xa0, len[4]); + + /* 5. And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, len[5]); + + /* 6. And the inner sequence. */ + macstart = p; + p = store_tag_length (p, TAG_SEQUENCE, len[6]); + + /* 7. Append all the buffers. */ + for (i=0; sequences[i].buffer; i++) + { + memcpy (p, sequences[i].buffer, sequences[i].length); + p += sequences[i].length; + } + + if (with_mac) + { + /* Intermezzo to compute the MAC. */ + maclen = p - macstart; + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf)) + { + gcry_free (result); + return NULL; + } + rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + if (rc) + { + log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc)); + gcry_free (result); + return NULL; + } + rc = gcry_md_setkey (md, keybuf, 20); + if (rc) + { + log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_free (result); + return NULL; + } + gcry_md_write (md, macstart, maclen); + + /* 8. Append the MAC template and fix it up. */ + memcpy (p, data_mactemplate, DIM (data_mactemplate)); + memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8); + memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20); + p += DIM (data_mactemplate); + gcry_md_close (md); + } + + /* Ready. */ + resultlen = p - result; + if (needed != resultlen) + log_debug ("length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)resultlen); + + *r_length = resultlen; + return result; +} + + +/* Build a DER encoded SEQUENCE with the key: + + SEQUENCE { + INTEGER 0 + SEQUENCE { + OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1) + NULL + } + OCTET STRING, encapsulates { + SEQUENCE { + INTEGER 0 + INTEGER + INTEGER + INTEGER + INTEGER + INTEGER + INTEGER + INTEGER + INTEGER + } + } + } +*/ + +static unsigned char * +build_key_sequence (gcry_mpi_t *kparms, size_t *r_length) +{ + int rc, i; + size_t needed, n; + unsigned char *plain, *p; + size_t plainlen; + size_t outseqlen, oidseqlen, octstrlen, inseqlen; + + needed = 3; /* The version(?) integer of value 0. */ + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("error formatting parameter: %s\n", gpg_strerror (rc)); + return NULL; + } + needed += n; + n = compute_tag_length (n); + if (!n) + return NULL; + needed += n; + } + if (i != 8) + { + log_error ("invalid parameters for p12_build\n"); + return NULL; + } + /* Now this all goes into a sequence. */ + inseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Encapsulate all into an octet string. */ + octstrlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + /* Prepend the object identifier sequence. */ + oidseqlen = 2 + DIM (oid_rsaEncryption) + 2; + needed += 2 + oidseqlen; + /* The version number. */ + needed += 3; + /* And finally put the whole thing into a sequence. */ + outseqlen = needed; + n = compute_tag_length (needed); + if (!n) + return NULL; + needed += n; + + /* allocate 8 extra bytes for padding */ + plain = gcry_malloc_secure (needed+8); + if (!plain) + { + log_error ("error allocating encryption buffer\n"); + return NULL; + } + + /* And now fill the plaintext buffer. */ + p = plain; + p = store_tag_length (p, TAG_SEQUENCE, outseqlen); + /* Store version. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + /* Store object identifier sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, oidseqlen); + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption)); + memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption)); + p += DIM (oid_rsaEncryption); + *p++ = TAG_NULL; + *p++ = 0; + /* Start with the octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, octstrlen); + p = store_tag_length (p, TAG_SEQUENCE, inseqlen); + /* Store the key parameters. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + for (i=0; kparms[i]; i++) + { + n = 0; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]); + if (rc) + { + log_error ("oops: error formatting parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p = store_tag_length (p, TAG_INTEGER, n); + + n = plain + needed - p; + rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]); + if (rc) + { + log_error ("oops: error storing parameter: %s\n", + gpg_strerror (rc)); + gcry_free (plain); + return NULL; + } + p += n; + } + + plainlen = p - plain; + assert (needed == plainlen); + /* Append some pad characters; we already allocated extra space. */ + n = 8 - plainlen % 8; + for (i=0; i < n; i++, plainlen++) + *p++ = n; + + *r_length = plainlen; + return plain; +} + + + +static unsigned char * +build_key_bag (unsigned char *buffer, size_t buflen, char *salt, + const unsigned char *sha1hash, const char *keyidstr, + size_t *r_length) +{ + size_t len[11], needed; + unsigned char *p, *keybag; + size_t keybaglen; + + /* Walk 11 steps down to collect the info: */ + + /* 10. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 9. Prepend the algorithm identifier. */ + needed += DIM (data_3desiter2048); + + /* 8. Put a sequence around. */ + len[8] = needed; + needed += compute_tag_length (needed); + + /* 7. Prepend a [0] tag. */ + len[7] = needed; + needed += compute_tag_length (needed); + + /* 6b. The attributes which are appended at the end. */ + if (sha1hash) + needed += DIM (data_attrtemplate) + 20; + + /* 6. Prepend the shroudedKeyBag OID. */ + needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + + /* 5+4. Put all into two sequences. */ + len[5] = needed; + needed += compute_tag_length ( needed); + len[4] = needed; + needed += compute_tag_length (needed); + + /* 3. This all goes into an octet string. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2. Prepend another [0] tag. */ + len[2] = needed; + needed += compute_tag_length (needed); + + /* 1. Prepend the data OID. */ + needed += 2 + DIM (oid_data); + + /* 0. Prepend another sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = keybag = gcry_malloc (needed); + if (!keybag) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 11 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 2. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[2]); + + /* 3. And an octet string. */ + p = store_tag_length (p, TAG_OCTET_STRING, len[3]); + + /* 4+5. Two sequences. */ + p = store_tag_length (p, TAG_SEQUENCE, len[4]); + p = store_tag_length (p, TAG_SEQUENCE, len[5]); + + /* 6. Store the shroudedKeyBag OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag, + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag)); + p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag); + + /* 7. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[7]); + + /* 8. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[8]); + + /* 9. Now for the pre-encoded algorithm identifier and the salt. */ + memcpy (p, data_3desiter2048, DIM (data_3desiter2048)); + memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8); + p += DIM (data_3desiter2048); + + /* 10. And the octet string with the encrypted data. */ + p = store_tag_length (p, TAG_OCTET_STRING, buflen); + memcpy (p, buffer, buflen); + p += buflen; + + /* Append the attributes whose length we calculated at step 2b. */ + if (sha1hash) + { + int i; + + memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); + for (i=0; i < 8; i++) + p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; + p += DIM (data_attrtemplate); + memcpy (p, sha1hash, 20); + p += 20; + } + + + keybaglen = p - keybag; + if (needed != keybaglen) + log_debug ("length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)keybaglen); + + *r_length = keybaglen; + return keybag; +} + + +static unsigned char * +build_cert_bag (unsigned char *buffer, size_t buflen, char *salt, + size_t *r_length) +{ + size_t len[9], needed; + unsigned char *p, *certbag; + size_t certbaglen; + + /* Walk 9 steps down to collect the info: */ + + /* 8. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 7. The algorithm identifier. */ + needed += DIM (data_rc2iter2048); + + /* 6. The data OID. */ + needed += 2 + DIM (oid_data); + + /* 5. A sequence. */ + len[5] = needed; + needed += compute_tag_length ( needed); + + /* 4. An integer. */ + needed += 3; + + /* 3. A sequence. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2. A [0] tag. */ + len[2] = needed; + needed += compute_tag_length (needed); + + /* 1. The encryptedData OID. */ + needed += 2 + DIM (oid_encryptedData); + + /* 0. The first sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = certbag = gcry_malloc (needed); + if (!certbag) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 9 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the encryptedData OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData)); + memcpy (p, oid_encryptedData, DIM (oid_encryptedData)); + p += DIM (oid_encryptedData); + + /* 2. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[2]); + + /* 3. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[3]); + + /* 4. Store the integer 0. */ + *p++ = TAG_INTEGER; + *p++ = 1; + *p++ = 0; + + /* 5. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[5]); + + /* 6. Store the data OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data)); + memcpy (p, oid_data, DIM (oid_data)); + p += DIM (oid_data); + + /* 7. Now for the pre-encoded algorithm identifier and the salt. */ + memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048)); + memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8); + p += DIM (data_rc2iter2048); + + /* 8. And finally the [0] tag with the encrypted data. */ + p = store_tag_length (p, 0x80, buflen); + memcpy (p, buffer, buflen); + p += buflen; + certbaglen = p - certbag; + + if (needed != certbaglen) + log_debug ("length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)certbaglen); + + *r_length = certbaglen; + return certbag; +} + + +static unsigned char * +build_cert_sequence (unsigned char *buffer, size_t buflen, + const unsigned char *sha1hash, const char *keyidstr, + size_t *r_length) +{ + size_t len[8], needed, n; + unsigned char *p, *certseq; + size_t certseqlen; + int i; + + assert (strlen (keyidstr) == 8); + + /* Walk 8 steps down to collect the info: */ + + /* 7. The data goes into an octet string. */ + needed = compute_tag_length (buflen); + needed += buflen; + + /* 6. A [0] tag. */ + len[6] = needed; + needed += compute_tag_length (needed); + + /* 5. An OID. */ + needed += 2 + DIM (oid_x509Certificate_for_pkcs_12); + + /* 4. A sequence. */ + len[4] = needed; + needed += compute_tag_length (needed); + + /* 3. A [0] tag. */ + len[3] = needed; + needed += compute_tag_length (needed); + + /* 2b. The attributes which are appended at the end. */ + if (sha1hash) + needed += DIM (data_attrtemplate) + 20; + + /* 2. An OID. */ + needed += 2 + DIM (oid_pkcs_12_CertBag); + + /* 1. A sequence. */ + len[1] = needed; + needed += compute_tag_length (needed); + + /* 0. The first sequence. */ + len[0] = needed; + needed += compute_tag_length (needed); + + /* Now that we have all length information, allocate a buffer. */ + p = certseq = gcry_malloc (needed + 8 /*(for padding)*/); + if (!certseq) + { + log_error ("error allocating buffer\n"); + return NULL; + } + + /* Walk 8 steps up to store the data. */ + + /* 0. Store the first sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[0]); + + /* 1. Store the second sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[1]); + + /* 2. Store the pkcs12-cert-bag OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag)); + memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag)); + p += DIM (oid_pkcs_12_CertBag); + + /* 3. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[3]); + + /* 4. Store a sequence. */ + p = store_tag_length (p, TAG_SEQUENCE, len[4]); + + /* 5. Store the x509Certificate OID. */ + p = store_tag_length (p, TAG_OBJECT_ID, + DIM (oid_x509Certificate_for_pkcs_12)); + memcpy (p, oid_x509Certificate_for_pkcs_12, + DIM (oid_x509Certificate_for_pkcs_12)); + p += DIM (oid_x509Certificate_for_pkcs_12); + + /* 6. Store a [0] tag. */ + p = store_tag_length (p, 0xa0, len[6]); + + /* 7. And the octet string with the actual certificate. */ + p = store_tag_length (p, TAG_OCTET_STRING, buflen); + memcpy (p, buffer, buflen); + p += buflen; + + /* Append the attributes whose length we calculated at step 2b. */ + if (sha1hash) + { + memcpy (p, data_attrtemplate, DIM (data_attrtemplate)); + for (i=0; i < 8; i++) + p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i]; + p += DIM (data_attrtemplate); + memcpy (p, sha1hash, 20); + p += 20; + } + + certseqlen = p - certseq; + if (needed != certseqlen) + log_debug ("length mismatch: %lu, %lu\n", + (unsigned long)needed, (unsigned long)certseqlen); + + /* Append some pad characters; we already allocated extra space. */ + n = 8 - certseqlen % 8; + for (i=0; i < n; i++, certseqlen++) + *p++ = n; + + *r_length = certseqlen; + return certseq; +} + + +/* Expect the RSA key parameters in KPARMS and a password in PW. + Create a PKCS structure from it and return it as well as the length + in R_LENGTH; return NULL in case of an error. If CHARSET is not + NULL, re-encode PW to that character set. */ +unsigned char * +p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen, + const char *pw, const char *charset, size_t *r_length) +{ + unsigned char *buffer = NULL; + size_t n, buflen; + char salt[8]; + struct buffer_s seqlist[3]; + int seqlistidx = 0; + unsigned char sha1hash[20]; + char keyidstr[8+1]; + char *pwbuf = NULL; + size_t pwbufsize = 0; + + n = buflen = 0; /* (avoid compiler warning). */ + memset (sha1hash, 0, 20); + *keyidstr = 0; + + if (charset && pw && *pw) + { + jnlib_iconv_t cd; + const char *inptr; + char *outptr; + size_t inbytes, outbytes; + + /* We assume that the converted passphrase is at max 2 times + longer than its utf-8 encoding. */ + pwbufsize = strlen (pw)*2 + 1; + pwbuf = gcry_malloc_secure (pwbufsize); + if (!pwbuf) + { + log_error ("out of secure memory while converting passphrase\n"); + goto failure; + } + + cd = jnlib_iconv_open (charset, "utf-8"); + if (cd == (jnlib_iconv_t)(-1)) + { + log_error ("can't convert passphrase to" + " requested charset `%s': %s\n", + charset, strerror (errno)); + gcry_free (pwbuf); + goto failure; + } + + inptr = pw; + inbytes = strlen (pw); + outptr = pwbuf; + outbytes = pwbufsize - 1; + if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes, + &outptr, &outbytes) == (size_t)-1) + { + log_error ("error converting passphrase to" + " requested charset `%s': %s\n", + charset, strerror (errno)); + gcry_free (pwbuf); + jnlib_iconv_close (cd); + goto failure; + } + *outptr = 0; + jnlib_iconv_close (cd); + pw = pwbuf; + } + + + if (cert && certlen) + { + /* Calculate the hash value we need for the bag attributes. */ + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen); + sprintf (keyidstr, "%02x%02x%02x%02x", + sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]); + + /* Encode the certificate. */ + buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr, + &buflen); + if (!buffer) + goto failure; + + /* Encrypt it. */ + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + crypt_block (buffer, buflen, salt, 8, 2048, pw, + GCRY_CIPHER_RFC2268_40, 1); + + /* Encode the encrypted stuff into a bag. */ + seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n); + seqlist[seqlistidx].length = n; + gcry_free (buffer); + buffer = NULL; + if (!seqlist[seqlistidx].buffer) + goto failure; + seqlistidx++; + } + + + if (kparms) + { + /* Encode the key. */ + buffer = build_key_sequence (kparms, &buflen); + if (!buffer) + goto failure; + + /* Encrypt it. */ + gcry_randomize (salt, 8, GCRY_STRONG_RANDOM); + crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1); + + /* Encode the encrypted stuff into a bag. */ + if (cert && certlen) + seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, + sha1hash, keyidstr, &n); + else + seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, + NULL, NULL, &n); + seqlist[seqlistidx].length = n; + gcry_free (buffer); + buffer = NULL; + if (!seqlist[seqlistidx].buffer) + goto failure; + seqlistidx++; + } + + seqlist[seqlistidx].buffer = NULL; + seqlist[seqlistidx].length = 0; + + buffer = create_final (seqlist, pw, &buflen); + + failure: + if (pwbuf) + { + wipememory (pwbuf, pwbufsize); + gcry_free (pwbuf); + } + for ( ; seqlistidx; seqlistidx--) + gcry_free (seqlist[seqlistidx].buffer); + + *r_length = buffer? buflen : 0; + return buffer; +} + + +#ifdef TEST + +static void +cert_cb (void *opaque, const unsigned char *cert, size_t certlen) +{ + printf ("got a certificate of %u bytes length\n", certlen); +} + +int +main (int argc, char **argv) +{ + FILE *fp; + struct stat st; + unsigned char *buf; + size_t buflen; + gcry_mpi_t *result; + + if (argc != 3) + { + fprintf (stderr, "usage: testp12 file passphrase\n"); + return 1; + } + + gcry_control (GCRYCTL_DISABLE_SECMEM, NULL); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, NULL); + + fp = fopen (argv[1], "rb"); + if (!fp) + { + fprintf (stderr, "can't open `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + + if (fstat (fileno(fp), &st)) + { + fprintf (stderr, "can't stat `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + + buflen = st.st_size; + buf = gcry_malloc (buflen+1); + if (!buf || fread (buf, buflen, 1, fp) != 1) + { + fprintf (stderr, "error reading `%s': %s\n", argv[1], strerror (errno)); + return 1; + } + fclose (fp); + + result = p12_parse (buf, buflen, argv[2], cert_cb, NULL); + if (result) + { + int i, rc; + unsigned char *tmpbuf; + + for (i=0; result[i]; i++) + { + rc = gcry_mpi_aprint (GCRYMPI_FMT_HEX, &tmpbuf, + NULL, result[i]); + if (rc) + printf ("%d: [error printing number: %s]\n", + i, gpg_strerror (rc)); + else + { + printf ("%d: %s\n", i, tmpbuf); + gcry_free (tmpbuf); + } + } + } + + return 0; + +} + +/* +Local Variables: +compile-command: "gcc -Wall -O0 -g -DTEST=1 -o minip12 minip12.c ../common/libcommon.a -L /usr/local/lib -lgcrypt -lgpg-error" +End: +*/ +#endif /* TEST */ diff --git a/sm/minip12.h b/sm/minip12.h new file mode 100644 index 000000000..f2af70957 --- /dev/null +++ b/sm/minip12.h @@ -0,0 +1,36 @@ +/* minip12.h - Global definitions for the minimal pkcs-12 implementation. + * Copyright (C) 2002, 2003 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 . + */ + +#ifndef MINIP12_H +#define MINIP12_H + +#include + +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, int *r_badpass); + +unsigned char *p12_build (gcry_mpi_t *kparms, + unsigned char *cert, size_t certlen, + const char *pw, const char *charset, + size_t *r_length); + + +#endif /*MINIP12_H*/ -- cgit v1.2.3