From 1a85ee9a431bd2243e0ad79ce5eefa78e274a491 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 2 Dec 2022 10:03:36 +0100 Subject: [PATCH] gpg: New export option "mode1003". * agent/command.c (cmd_export_key): Add option --mode1003. (command_has_option): Ditto. * g10/build-packet.c (do_key): Implement mode 1003. * g10/parse-packet.c (parse_key): Ditto. * g10/options.h (EXPORT_MODE1003): New.o * g10/call-agent.c (agent_export_key): Add arg mode1003. * g10/export.c (parse_export_options): Add "mode1003" (secret_key_to_mode1003): New. (receive_seckey_from_agent): Add arg mode1003. (do_export_one_keyblock): Pass option down. -- This option allows to export a secret key in GnuPG's native format. Thus no re-encryption is required and further the public key parameters are also authenticated if a protection passphrase has been used. Note that --import is not yet able to handle this new mode. Although old version of GnuPG will bail out with "invalid packet" if a mode1003 exported secret key is seen. --- agent/command.c | 36 +++++--- doc/DETAILS | 8 ++ doc/gpg.texi | 10 +++ g10/build-packet.c | 24 +++++- g10/call-agent.c | 20 +++-- g10/call-agent.h | 2 +- g10/export.c | 202 +++++++++++++++++++++++++++++++++++++++++++-- g10/keygen.c | 2 +- g10/main.h | 2 +- g10/options.h | 1 + g10/parse-packet.c | 48 +++++++++-- 11 files changed, 320 insertions(+), 35 deletions(-) diff --git a/agent/command.c b/agent/command.c index 840f9f38e..8b5434bfb 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2935,7 +2935,7 @@ cmd_import_key (assuan_context_t ctx, char *line) static const char hlp_export_key[] = - "EXPORT_KEY [--cache-nonce=] [--openpgp] \n" + "EXPORT_KEY [--cache-nonce=] [--openpgp|--mode1003] \n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" @@ -2943,9 +2943,10 @@ static const char hlp_export_key[] = "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" - "compatible passphrase-protected form. Without --openpgp, the secret key\n" - "material will be exported in the clear (after prompting the user to unlock\n" - "it, if needed).\n"; + "compatible passphrase-protected form. If --mode1003 is use the secret key\n" + "is exported as s-expression as storred locally. Without those options,\n" + "the secret key material will be exported in the clear (after prompting\n" + "the user to unlock it, if needed).\n"; static gpg_error_t cmd_export_key (assuan_context_t ctx, char *line) { @@ -2958,7 +2959,7 @@ cmd_export_key (assuan_context_t ctx, char *line) gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; - int openpgp; + int openpgp, mode1003; char *cache_nonce; char *passphrase = NULL; unsigned char *shadow_info = NULL; @@ -2969,6 +2970,10 @@ cmd_export_key (assuan_context_t ctx, char *line) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); openpgp = has_option (line, "--openpgp"); + mode1003 = has_option (line, "--mode1003"); + if (mode1003) + openpgp = 0; + cache_nonce = option_value (line, "--cache-nonce"); if (cache_nonce) { @@ -3003,11 +3008,17 @@ cmd_export_key (assuan_context_t ctx, char *line) } /* Get the key from the file. With the openpgp flag we also ask for - the passphrase so that we can use it to re-encrypt it. */ - err = agent_key_from_file (ctrl, cache_nonce, - ctrl->server_local->keydesc, grip, - &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, - openpgp ? &passphrase : NULL, NULL); + * the passphrase so that we can use it to re-encrypt it. In + * mode1003 we return the key as-is. FIXME: if the key is still in + * OpenPGP-native mode we should first convert it to our internal + * protection. */ + if (mode1003) + err = agent_raw_key_from_file (ctrl, grip, &s_skey, NULL); + else + err = agent_key_from_file (ctrl, cache_nonce, + ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, + openpgp ? &passphrase : NULL, NULL); if (err) goto leave; if (shadow_info) @@ -4150,6 +4161,11 @@ command_has_option (const char *cmd, const char *cmdopt) if (!strcmp (cmdopt, "newsymkey")) return 1; } + else if (!strcmp (cmd, "EXPORT_KEY")) + { + if (!strcmp (cmdopt, "mode1003")) + return 1; + } return 0; } diff --git a/doc/DETAILS b/doc/DETAILS index 9581f3032..a3fe802a2 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1504,6 +1504,14 @@ CREATE TABLE signatures ( - One octet with the length of the following serial number. - The serial number. Regardless of what the length octet indicates no more than 16 octets are stored. + - 3 :: The internal representation of a private key: For v4 keys we + first write 4 octets big endian length of the following + s-expression with the protected or unprotected private key; + for v5 keys this is not necessarily because that length + header is always there. The actual data are N octets of + s-expression. Any protection (including the real S2K) is + part of that data. Note that the public key aparemters are + repeated in th s-expression. Note that gpg stores the GNU S2K Extension Number internally as an S2K Specifier with an offset of 1000. diff --git a/doc/gpg.texi b/doc/gpg.texi index c82a4aa4d..1a3cb9e25 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -2721,6 +2721,16 @@ opposite meaning. The options are: each record to allow diverting the records to the corresponding zone file. + @item mode1003 + Enable the use of a new secret key export format. This format + avoids the re-encryption as required with the current OpenPGP format + and also improves the security of the secret key if it has been + protected with a passphrase. Note that an unprotected key is + exported as-is and thus not secure; the general rule to convey + secret keys in an OpenPGP encrypted file still applies with this + mode. Versions of GnuPG before 2.4.0 are not able to import such a + secret file. + @end table @item --with-colons diff --git a/g10/build-packet.c b/g10/build-packet.c index cc953557d..f33d156b3 100644 --- a/g10/build-packet.c +++ b/g10/build-packet.c @@ -674,7 +674,8 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) count += 8; /* Salt. */ if (ski->s2k.mode == 3) count++; /* S2K.COUNT */ - if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002) + if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002 + && ski->s2k.mode != 1003) count += ski->ivlen; iobuf_put (a, count); @@ -704,8 +705,9 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) if (ski->s2k.mode == 3) iobuf_put (a, ski->s2k.count); - /* For our special modes 1001, 1002 we do not need an IV. */ - if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002) + /* For our special modes 1001..1003 we do not need an IV. */ + if (ski->s2k.mode != 1001 && ski->s2k.mode != 1002 + && ski->s2k.mode != 1003) iobuf_write (a, ski->iv, ski->ivlen); } @@ -733,6 +735,22 @@ do_key (iobuf_t out, int ctb, PKT_public_key *pk) /* The serial number gets stored in the IV field. */ iobuf_write (a, ski->iv, ski->ivlen); } + else if (ski->s2k.mode == 1003) + { + /* GnuPG extension - Store raw s-expression. */ + byte *p; + unsigned int ndatabits; + + log_assert (gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE)); + + p = gcry_mpi_get_opaque (pk->pkey[npkey], &ndatabits); + /* For v5 keys we first write the number of octets of the + * following key material. */ + if (is_v5) + write_32 (a, p? (ndatabits+7)/8 : 0); + if (p) + iobuf_write (a, p, (ndatabits+7)/8 ); + } else if (ski->is_protected) { /* The secret key is protected - write it out as it is. */ diff --git a/g10/call-agent.c b/g10/call-agent.c index 27b5cacfb..66812e998 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -2997,13 +2997,15 @@ agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, keygrip, DESC a prompt to be displayed with the agent's passphrase question (needs to be plus+percent escaped). if OPENPGP_PROTECTED is not zero, ensure that the key material is returned in RFC - 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is - not NULL the agent is advised to first try a passphrase associated - with that nonce. On success the key is stored as a canonical - S-expression at R_RESULT and R_RESULTLEN. */ + 4880-compatible passphrased-protected form; if instead MODE1003 is + not zero the raw gpg-agent private key format is requested (either + protected or unprotected). If CACHE_NONCE_ADDR is not NULL the + agent is advised to first try a passphrase associated with that + nonce. On success the key is stored as a canonical S-expression at + R_RESULT and R_RESULTLEN. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, - int openpgp_protected, char **cache_nonce_addr, + int openpgp_protected, int mode1003, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, u32 *keyid, u32 *mainkeyid, int pubkey_algo) { @@ -3028,6 +3030,12 @@ agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, return err; dfltparm.ctx = agent_ctx; + /* Check that the gpg-agent supports the --mode1003 option. */ + if (mode1003 && assuan_transact (agent_ctx, + "GETINFO cmd_has_option EXPORT_KEY mode1003", + NULL, NULL, NULL, NULL, NULL, NULL)) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); @@ -3038,7 +3046,7 @@ agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, } snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", - openpgp_protected ? "--openpgp ":"", + mode1003? "--mode1003" : openpgp_protected ? "--openpgp ":"", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); diff --git a/g10/call-agent.h b/g10/call-agent.h index a4cbc3162..a3f234ade 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -231,7 +231,7 @@ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, /* Receive a key from the agent. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, int openpgp_protected, - char **cache_nonce_addr, + int mode1003, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen, u32 *keyid, u32 *mainkeyid, int pubkey_algo); diff --git a/g10/export.c b/g10/export.c index 3d4413068..cab00d10c 100644 --- a/g10/export.c +++ b/g10/export.c @@ -2,6 +2,7 @@ * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, * 2005, 2010 Free Software Foundation, Inc. * Copyright (C) 1998-2016 Werner Koch + * Copyright (C) 2022 g10 Code GmbH * * This file is part of GnuPG. * @@ -140,6 +141,9 @@ parse_export_options(char *str,unsigned int *options,int noisy) N_("use the GnuPG key backup format")}, {"export-backup", EXPORT_BACKUP, NULL, NULL }, + {"mode1003", EXPORT_MODE1003, NULL, + N_("export secret keys using the GnuPG format") }, + /* Aliases for backward compatibility */ {"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL}, {"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL}, @@ -639,6 +643,183 @@ canon_pk_algo (enum gcry_pk_algos algo) } +/* Take an s-expression wit the public and private key and change the + * parameter array in PK to include the secret parameters. */ +static gpg_error_t +secret_key_to_mode1003 (gcry_sexp_t s_key, PKT_public_key *pk) +{ + gpg_error_t err; + gcry_sexp_t list = NULL; + gcry_sexp_t l2; + enum gcry_pk_algos pk_algo; + struct seckey_info *ski; + int idx; + char *string; + size_t npkey, nskey; + gcry_mpi_t pub_params[10] = { NULL }; + + /* We look for a private-key, then the first element in it tells us + the type */ + list = gcry_sexp_find_token (s_key, "protected-private-key", 0); + if (!list) + list = gcry_sexp_find_token (s_key, "private-key", 0); + if (!list) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + log_assert (!pk->seckey_info); + + /* Parse the gcrypt PK algo and check that it is okay. */ + l2 = gcry_sexp_cadr (list); + if (!l2) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + gcry_sexp_release (list); + list = l2; + string = gcry_sexp_nth_string (list, 0); + if (!string) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + pk_algo = gcry_pk_map_name (string); + xfree (string); string = NULL; + if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey) + || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey) + || !npkey || npkey >= nskey) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + /* Check that the pubkey algo and the received parameters matches + * those from the public key. */ + switch (canon_pk_algo (pk_algo)) + { + case GCRY_PK_RSA: + if (!is_RSA (pk->pubkey_algo) || npkey != 2) + err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ + else + err = gcry_sexp_extract_param (list, NULL, "ne", + &pub_params[0], + &pub_params[1], + NULL); + break; + + case GCRY_PK_DSA: + if (!is_DSA (pk->pubkey_algo) || npkey != 4) + err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ + else + err = gcry_sexp_extract_param (list, NULL, "pqgy", + &pub_params[0], + &pub_params[1], + &pub_params[2], + &pub_params[3], + NULL); + break; + + case GCRY_PK_ELG: + if (!is_ELGAMAL (pk->pubkey_algo) || npkey != 3) + err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ + else + err = gcry_sexp_extract_param (list, NULL, "pgy", + &pub_params[0], + &pub_params[1], + &pub_params[2], + NULL); + break; + + case GCRY_PK_ECC: + err = 0; + if (!(pk->pubkey_algo == PUBKEY_ALGO_ECDSA + || pk->pubkey_algo == PUBKEY_ALGO_ECDH + || pk->pubkey_algo == PUBKEY_ALGO_EDDSA)) + { + err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ + goto leave; + } + npkey = 2; + if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) + npkey++; + /* Dedicated check of the curve. */ + pub_params[0] = NULL; + err = match_curve_skey_pk (list, pk); + if (err) + goto leave; + /* ... and of the Q parameter. */ + err = sexp_extract_param_sos (list, "q", &pub_params[1]); + if (!err && (gcry_mpi_cmp (pk->pkey[1], pub_params[1]))) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Unknown. */ + break; + } + if (err) + goto leave; + + nskey = npkey + 1; /* We only have one skey param. */ + if (nskey > PUBKEY_MAX_NSKEY) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + /* Check that the public key parameters match. For ECC we already + * did this in the switch above. */ + if (canon_pk_algo (pk_algo) != GCRY_PK_ECC) + { + for (idx=0; idx < npkey; idx++) + if (gcry_mpi_cmp (pk->pkey[idx], pub_params[idx])) + { + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + } + + /* Store the maybe protected secret key as an s-expression. */ + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!ski) + { + err = gpg_error_from_syserror (); + goto leave; + } + + pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); + if (!ski) + { + err = gpg_error_from_syserror (); + goto leave; + } + + ski->is_protected = 1; + ski->s2k.mode = 1003; + + { + unsigned char *buf; + size_t buflen; + + err = make_canon_sexp (s_key, &buf, &buflen); + if (err) + goto leave; + pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, buf, buflen*8); + for (idx=npkey+1; idx < PUBKEY_MAX_NSKEY; idx++) + pk->pkey[idx] = NULL; + } + + leave: + gcry_sexp_release (list); + for (idx=0; idx < DIM(pub_params); idx++) + gcry_mpi_release (pub_params[idx]); + return err; +} + + /* Take a cleartext dump of a secret key in PK and change the * parameter array in PK to include the secret parameters. */ static gpg_error_t @@ -1248,6 +1429,11 @@ print_status_exported (PKT_public_key *pk) * passphrase-protected. Otherwise, store secret key material in the * clear. * + * If MODE1003 is set, the key is requested in raw GnuPG format from + * the agent. This usually does not require a passphrase unless the + * gpg-agent has not yet used the key and needs to convert it to its + * internal format first. + * * CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals. * * If PK is NULL, the raw key is returned (e.g. for ssh export) at @@ -1255,7 +1441,7 @@ print_status_exported (PKT_public_key *pk) */ gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, - int cleartext, + int cleartext, int mode1003, char **cache_nonce_addr, const char *hexgrip, PKT_public_key *pk, gcry_sexp_t *r_key) { @@ -1275,7 +1461,7 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, if (pk) { prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT, 1); - err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, + err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, mode1003, cache_nonce_addr, &wrappedkey, &wrappedkeylen, pk->keyid, pk->main_keyid, pk->pubkey_algo); @@ -1283,7 +1469,7 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, else { prompt = gpg_format_keydesc (ctrl, NULL, FORMAT_KEYDESC_KEYGRIP, 1); - err = agent_export_key (ctrl, hexgrip, prompt, 0, + err = agent_export_key (ctrl, hexgrip, prompt, 0, 0, NULL, &wrappedkey, &wrappedkeylen, NULL, NULL, 0); @@ -1314,7 +1500,9 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen); if (!err) { - if (pk && cleartext) + if (pk && mode1003) + err = secret_key_to_mode1003 (s_skey, pk); + else if (pk && cleartext) err = cleartext_secret_key_to_openpgp (s_skey, pk); else if (pk) err = transfer_format_to_openpgp (s_skey, pk); @@ -1832,7 +2020,9 @@ do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, else if (!err) { err = receive_seckey_from_agent (ctrl, cipherhd, - cleartext, &cache_nonce, + cleartext, + !!(options & EXPORT_MODE1003), + &cache_nonce, hexgrip, pk, NULL); if (err) { @@ -2781,7 +2971,7 @@ export_secret_ssh_key (ctrl_t ctrl, const char *userid) if ((err = get_keywrap_key (ctrl, &cipherhd))) goto leave; - err = receive_seckey_from_agent (ctrl, cipherhd, 0, NULL, hexgrip, NULL, + err = receive_seckey_from_agent (ctrl, cipherhd, 0, 0, NULL, hexgrip, NULL, &skey); if (err) goto leave; diff --git a/g10/keygen.c b/g10/keygen.c index 8e2c092fc..4dcf7a494 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -5286,7 +5286,7 @@ card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk, goto leave; } - err = receive_seckey_from_agent (ctrl, cipherhd, 0, + err = receive_seckey_from_agent (ctrl, cipherhd, 0, 0, &cache_nonce, hexgrip, sk, NULL); if (err) { diff --git a/g10/main.h b/g10/main.h index 968465ebd..62d2651be 100644 --- a/g10/main.h +++ b/g10/main.h @@ -431,7 +431,7 @@ gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, void **r_data, size_t *r_datalen); gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, - int cleartext, + int cleartext, int mode1003, char **cache_nonce_addr, const char *hexgrip, PKT_public_key *pk, gcry_sexp_t *r_key); diff --git a/g10/options.h b/g10/options.h index ed4865168..c10862687 100644 --- a/g10/options.h +++ b/g10/options.h @@ -407,6 +407,7 @@ EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode; #define EXPORT_DANE_FORMAT (1<<7) #define EXPORT_BACKUP (1<<10) #define EXPORT_REVOCS (1<<11) +#define EXPORT_MODE1003 (1<<12) #define LIST_SHOW_PHOTOS (1<<0) #define LIST_SHOW_POLICY_URLS (1<<1) diff --git a/g10/parse-packet.c b/g10/parse-packet.c index b6aebbb69..a033732ec 100644 --- a/g10/parse-packet.c +++ b/g10/parse-packet.c @@ -2752,11 +2752,15 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, break; case 1001: if (list_mode) - es_fprintf (listfp, "\tgnu-dummy S2K"); + es_fprintf (listfp, "\tgnu-dummy"); break; case 1002: if (list_mode) - es_fprintf (listfp, "\tgnu-divert-to-card S2K"); + es_fprintf (listfp, "\tgnu-divert-to-card"); + break; + case 1003: + if (list_mode) + es_fprintf (listfp, "\tgnu-mode1003"); break; default: if (list_mode) @@ -2768,7 +2772,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, } /* Print some info. */ - if (list_mode) + if (list_mode && ski->s2k.mode != 1003) { es_fprintf (listfp, ", algo: %d,%s hash: %d", ski->algo, @@ -2779,8 +2783,9 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, es_fprintf (listfp, ", salt: "); es_write_hexstring (listfp, ski->s2k.salt, 8, 0, NULL); } - es_putc ('\n', listfp); - } + } + if (list_mode) + es_putc ('\n', listfp); /* Read remaining protection parameters. */ if (ski->s2k.mode == 3) @@ -2838,7 +2843,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, ski->ivlen = openpgp_cipher_blocklen (ski->algo); log_assert (ski->ivlen <= sizeof (temp)); - if (ski->s2k.mode == 1001) + if (ski->s2k.mode == 1001 || ski->s2k.mode == 1003) ski->ivlen = 0; else if (ski->s2k.mode == 1002) ski->ivlen = snlen < 16 ? snlen : 16; @@ -2850,7 +2855,7 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, } for (i = 0; i < ski->ivlen; i++, pktlen--) temp[i] = iobuf_get_noeof (inp); - if (list_mode) + if (list_mode && ski->s2k.mode != 1003) { es_fprintf (listfp, ski->s2k.mode == 1002 ? "\tserial-number: " @@ -2888,6 +2893,35 @@ parse_key (IOBUF inp, int pkttype, unsigned long pktlen, 10 * 8); pktlen = 0; } + else if (ski->s2k.mode == 1003) + { + void *tmpp; + + if (pktlen < 2) /* At least two bytes for parenthesis. */ + { + err = gpg_error (GPG_ERR_INV_PACKET); + goto leave; + } + + tmpp = read_rest (inp, pktlen); + if (list_mode) + { + if (mpi_print_mode) + { + char *tmpsxp = canon_sexp_to_string (tmpp, pktlen); + es_fprintf (listfp, "\tskey[%d]: %s\n", npkey, + tmpsxp? trim_trailing_spaces (tmpsxp) + /* */: "[invalid S-expression]"); + xfree (tmpsxp); + } + else + es_fprintf (listfp, "\tskey[%d]: [s-expression %lu octets]\n", + npkey, pktlen); + } + pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, + tmpp, tmpp? pktlen * 8 : 0); + pktlen = 0; + } else if (ski->is_protected) { void *tmpp;