diff options
| author | Werner Koch <[email protected]> | 2025-11-16 16:49:52 +0000 |
|---|---|---|
| committer | Werner Koch <[email protected]> | 2025-11-16 16:57:14 +0000 |
| commit | 47bab26daf035ffdce97e4957bdb6ad12dbea506 (patch) | |
| tree | efb6029844d8de1169d206730cee85af50fb2760 | |
| parent | gpg: Change the mode1003 format for composite keys. (diff) | |
| download | gnupg-47bab26daf035ffdce97e4957bdb6ad12dbea506.tar.gz gnupg-47bab26daf035ffdce97e4957bdb6ad12dbea506.zip | |
gpg: Allow the import of Kyber secret keys.
* g10/import.c (transfer_secret_keys): Handle mode 1003.
* g10/call-agent.c (agent_import_key): Add arg mode1003.
* common/sexputil.c (make_canon_sexp): Create in secmem when the input
was in secmem.
* agent/findkey.c (agent_write_private_key): Add arg 'linkattr' and
change all callers.
* agent/command.c (cmd_import_key): Add option '--mode1003'.
Reorganize code and implement support for composite keys.
--
GnuPG-bug-id: 7315
| -rw-r--r-- | agent/agent.h | 2 | ||||
| -rw-r--r-- | agent/command-ssh.c | 2 | ||||
| -rw-r--r-- | agent/command.c | 154 | ||||
| -rw-r--r-- | agent/cvt-openpgp.c | 4 | ||||
| -rw-r--r-- | agent/divert-tpm2.c | 4 | ||||
| -rw-r--r-- | agent/findkey.c | 16 | ||||
| -rw-r--r-- | agent/genkey.c | 2 | ||||
| -rw-r--r-- | agent/protect-tool.c | 4 | ||||
| -rw-r--r-- | common/sexputil.c | 2 | ||||
| -rw-r--r-- | g10/call-agent.c | 12 | ||||
| -rw-r--r-- | g10/call-agent.h | 2 | ||||
| -rw-r--r-- | g10/import.c | 29 |
12 files changed, 174 insertions, 59 deletions
diff --git a/agent/agent.h b/agent/agent.h index 626bf48c9..efdfe5b40 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -479,7 +479,7 @@ gpg_error_t agent_write_private_key (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *dispserialno, - time_t timestamp); + time_t timestamp, const char *linkattr); gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ab54a403f..ff4ca058e 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3334,7 +3334,7 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, /* Store this key to our key storage. We do not store a creation * timestamp because we simply do not know. */ err = agent_write_private_key (ctrl, key_grip_raw, buffer, buffer_n, 0, - NULL, NULL, NULL, 0); + NULL, NULL, NULL, 0, NULL); if (err) goto out; diff --git a/agent/command.c b/agent/command.c index d1ff8e27c..a50cbce5a 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2813,7 +2813,7 @@ cmd_keywrap_key (assuan_context_t ctx, char *line) static const char hlp_import_key[] = - "IMPORT_KEY [--unattended] [--force] [--timestamp=<isodate>]\n" + "IMPORT_KEY [--unattended] [--force] [--mode1003] [--timestamp=<isodate>]\n" " [<cache_nonce>]\n" "\n" "Import a secret key into the key store. The key is expected to be\n" @@ -2832,7 +2832,7 @@ cmd_import_key (assuan_context_t ctx, char *line) gpg_error_t err; int opt_unattended; time_t opt_timestamp; - int force; + int mode1003, force; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; @@ -2841,11 +2841,18 @@ cmd_import_key (assuan_context_t ctx, char *line) char *passphrase = NULL; unsigned char *finalkey = NULL; size_t finalkeylen; - unsigned char grip[20]; - gcry_sexp_t openpgp_sexp = NULL; + unsigned char grip1[KEYGRIP_LEN] = { 0 }; + unsigned char grip2[KEYGRIP_LEN] = { 0 }; + char hexgrip[2*KEYGRIP_LEN+1]; + gcry_sexp_t keydata = NULL; + gcry_sexp_t skey1 = NULL; /* Part 1 of a composite key. */ + gcry_sexp_t skey2 = NULL; /* Part 2 of a composite key. */ char *cache_nonce = NULL; char *p; const char *s; + const char *tag; + size_t taglen; + enum { KEYDATA_NORMAL,KEYDATA_PGP_TRANSFER, KEYDATA_COMPOSITE } keydata_type; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); @@ -2857,6 +2864,7 @@ cmd_import_key (assuan_context_t ctx, char *line) } opt_unattended = has_option (line, "--unattended"); + mode1003 = has_option (line, "--mode1003"); force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { @@ -2919,45 +2927,44 @@ cmd_import_key (assuan_context_t ctx, char *line) xfree (wrappedkey); wrappedkey = NULL; + /* Check what kind of key we received. */ 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); + err = gcry_sexp_sscan (&keydata, NULL, key, realkeylen); if (err) + goto leave; + tag = gcry_sexp_nth_data (keydata, 0, &taglen); + if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) + keydata_type = KEYDATA_PGP_TRANSFER; + else if (tag && taglen == 13 && !memcmp (tag, "composite-key", 13)) + keydata_type = KEYDATA_COMPOSITE; + else if (gcry_pk_get_keygrip (keydata, grip1)) + keydata_type = KEYDATA_NORMAL; + else /* get_keygrip failed */ { - /* This might be due to an unsupported S-expression format. - Check whether this is openpgp-private-key and trigger that - import code. */ - if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen)) - { - const char *tag; - size_t taglen; + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } - tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen); - if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) - ; - else - { - gcry_sexp_release (openpgp_sexp); - openpgp_sexp = NULL; - } - } - if (!openpgp_sexp) - goto leave; /* Note that ERR is still set. */ + if (opt_unattended && keydata_type != KEYDATA_PGP_TRANSFER) + { + err = set_error (GPG_ERR_ASS_PARAMETER, + "\"--unattended\" may only be used with OpenPGP keys"); + goto leave; } - if (openpgp_sexp) + if (keydata_type == KEYDATA_PGP_TRANSFER && !mode1003) { /* In most cases the key is encrypted and thus the conversion - function from the OpenPGP format to our internal format will - ask for a passphrase. That passphrase will be returned and - used to protect the key using the same code as for regular - key import. */ - + * function from the OpenPGP format to our internal format will + * ask for a passphrase. That passphrase will be returned and + * used to protect the key using the same code as for regular + * key import. */ xfree (key); key = NULL; - err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip, + err = convert_from_openpgp (ctrl, keydata, force, grip1, ctrl->server_local->keydesc, cache_nonce, &key, opt_unattended? NULL : &passphrase); if (err) @@ -2980,16 +2987,69 @@ cmd_import_key (assuan_context_t ctx, char *line) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } - else if (opt_unattended) + else if (keydata_type == KEYDATA_COMPOSITE) { - err = set_error (GPG_ERR_ASS_PARAMETER, - "\"--unattended\" may only be used with OpenPGP keys"); - goto leave; + if (!mode1003) + { + err = set_error (GPG_ERR_ASS_PARAMETER, + "\"--mode1003\" required for composite keys"); + goto leave; + } + /* Split the key up. */ + skey1 = gcry_sexp_nth (keydata, 1); + skey2 = gcry_sexp_nth (keydata, 2); + if (!skey1 || !skey2) + { + log_error ("broken composite key detected\n"); + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + if (!gcry_pk_get_keygrip (skey1, grip1) + || !gcry_pk_get_keygrip (skey2, grip2)) + { + log_error ("keygrip computation failed for" + " at least one part of composite key\n"); + err = gpg_error (GPG_ERR_BAD_PUBKEY); + goto leave; + } + + /* Test whether a key already exists. We do not yet implement + * FORCE because gpg is not yet able to correcly detect that + * both keys are on a smartcard (which is the only sensible way + * to allow overwriting both private keys. */ + if (!agent_key_available (ctrl, grip1) + || !agent_key_available (ctrl, grip2)) + { + log_error ("at least one part of a composite key already exists\n"); + err = gpg_error (GPG_ERR_EEXIST); + goto leave; + } + + /* Convert the key parts into canonical form and write them. */ + xfree (key); + bin2hex (grip2, KEYGRIP_LEN, hexgrip); + err = make_canon_sexp (skey1, &key, &keylen); + if (!err) + err = agent_write_private_key (ctrl, grip1, key, keylen, 0, NULL, + NULL, NULL, opt_timestamp, hexgrip); + xfree (key); key = NULL; + bin2hex (grip1, KEYGRIP_LEN, hexgrip); + if (!err) + err = make_canon_sexp (skey2, &key, &keylen); + if (!err) + err = agent_write_private_key (ctrl, grip2, key, keylen, 0, NULL, + NULL, NULL, opt_timestamp, hexgrip); + if (err) + goto leave; } - else + else /* KEYDATA_NORMAL. */ { - if (!force && !agent_key_available (ctrl, grip)) + if (!force && !agent_key_available (ctrl, grip1)) err = gpg_error (GPG_ERR_EEXIST); + else if (mode1003) + { + /* (No passphrase used) */ + } else { char *prompt = xtryasprintf @@ -3005,20 +3065,25 @@ cmd_import_key (assuan_context_t ctx, char *line) goto leave; } - if (passphrase) + if (keydata_type == KEYDATA_COMPOSITE) + ; /* Has already been written. */ + else if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count); if (!err) - err = agent_write_private_key (ctrl, grip, finalkey, finalkeylen, force, - NULL, NULL, NULL, opt_timestamp); + err = agent_write_private_key (ctrl, grip1, + finalkey, finalkeylen, force, + NULL, NULL, NULL, opt_timestamp, NULL); } else - err = agent_write_private_key (ctrl, grip, key, realkeylen, force, - NULL, NULL, NULL, opt_timestamp); + err = agent_write_private_key (ctrl, grip1, key, realkeylen, force, + NULL, NULL, NULL, opt_timestamp, NULL); leave: - gcry_sexp_release (openpgp_sexp); + gcry_sexp_release (skey1); + gcry_sexp_release (skey2); + gcry_sexp_release (keydata); xfree (finalkey); xfree (passphrase); xfree (key); @@ -4332,6 +4397,11 @@ command_has_option (const char *cmd, const char *cmdopt) if (!strcmp (cmdopt, "mode1003")) return 1; } + else if (!strcmp (cmd, "IMPORT_KEY")) + { + if (!strcmp (cmdopt, "mode1003")) + return 1; + } return 0; } diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 420dbb464..2dd5f8b04 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1150,7 +1150,7 @@ convert_from_openpgp_native (ctrl_t ctrl, agent_write_private_key (ctrl, grip, protectedkey, protectedkeylen, - 1, NULL, NULL, NULL, 0); + 1, NULL, NULL, NULL, 0, NULL); xfree (protectedkey); } else @@ -1159,7 +1159,7 @@ convert_from_openpgp_native (ctrl_t ctrl, agent_write_private_key (ctrl, grip, *r_key, gcry_sexp_canon_len (*r_key, 0, NULL,NULL), - 1, NULL, NULL, NULL, 0); + 1, NULL, NULL, NULL, 0, NULL); } } diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c index b9e8784bd..5500c07f1 100644 --- a/agent/divert-tpm2.c +++ b/agent/divert-tpm2.c @@ -59,7 +59,7 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); err = agent_write_private_key (ctrl, grip, shdkey, len, 1 /*force*/, - NULL, NULL, NULL, 0); + NULL, NULL, NULL, 0, NULL); xfree (shdkey); if (err) { @@ -72,7 +72,7 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, pkbuf, len); err1 = agent_write_private_key (ctrl, grip, pkbuf, len, 1 /*force*/, - NULL, NULL, NULL, 0); + NULL, NULL, NULL, 0, NULL); xfree(pkbuf); if (err1) log_error ("error trying to restore private key: %s\n", diff --git a/agent/findkey.c b/agent/findkey.c index 4ca83bce4..954199fcd 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -132,14 +132,15 @@ linefeed_to_percent0A (const char *string) * GRIP will get overwritten. If SERIALNO and KEYREF are given a * Token line is added to the key if the extended format is used. If * TIMESTAMP is not zero and the key does not yet exists it will be - * recorded as creation date. */ + * recorded as creation date. If LINKATTR is not NULL a Link: entry + * with that value will also be written. */ gpg_error_t agent_write_private_key (ctrl_t ctrl, const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, const char *dispserialno, - time_t timestamp) + time_t timestamp, const char *linkattr) { gpg_error_t err; char *fname = NULL; @@ -309,6 +310,15 @@ agent_write_private_key (ctrl_t ctrl, goto leave; } + /* Write a link attribute if supplied. */ + if (linkattr && *linkattr) + { + err = nvc_add (pk, "Link:", linkattr); + if (err) + goto leave; + } + + /* Check whether we need to write the file at all. */ if (!nvc_modified (pk, 0)) { @@ -2104,7 +2114,7 @@ agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip, len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); err = agent_write_private_key (ctrl, grip, shdkey, len, force, - serialno, keyid, dispserialno, 0); + serialno, keyid, dispserialno, 0, NULL); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); diff --git a/agent/genkey.c b/agent/genkey.c index 0fb94350d..b1b692d61 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -119,7 +119,7 @@ store_key (ctrl_t ctrl, gcry_sexp_t private, } else err = agent_write_private_key (ctrl, grip, buf, len, force, - NULL, NULL, NULL, timestamp); + NULL, NULL, NULL, timestamp, NULL); if (!err) { diff --git a/agent/protect-tool.c b/agent/protect-tool.c index c6450a20e..40e9aed76 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -818,7 +818,8 @@ gpg_error_t agent_write_private_key (ctrl_t ctrl, const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, - const char *dispserialno, time_t timestamp) + const char *dispserialno, time_t timestamp, + const char *linkattr) { char hexgrip[40+4+1]; char *p; @@ -829,6 +830,7 @@ agent_write_private_key (ctrl_t ctrl, const unsigned char *grip, (void)keyref; (void)timestamp; (void)dispserialno; + (void)linkattr; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); diff --git a/common/sexputil.c b/common/sexputil.c index fcd15ebc6..83c3176ea 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -153,7 +153,7 @@ make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen) len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); if (!len) return gpg_error (GPG_ERR_BUG); - buf = xtrymalloc (len); + buf = gcry_is_secure (sexp)? xtrymalloc_secure (len) : xtrymalloc (len); if (!buf) return gpg_error_from_syserror (); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len); diff --git a/g10/call-agent.c b/g10/call-agent.c index bba6fa833..a1a48c75c 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -3106,7 +3106,8 @@ inq_import_key_parms (void *opaque, const char *line) /* Call the agent to import a key into the agent. */ gpg_error_t -agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, +agent_import_key (ctrl_t ctrl, const char *desc, int mode1003, + char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp) { @@ -3128,6 +3129,12 @@ agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, 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 IMPORT_KEY mode1003", + NULL, NULL, NULL, NULL, NULL, NULL)) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + /* Do not use our cache of secret keygrips anymore - this command * would otherwise requiring to update that cache. */ if (ctrl && ctrl->secret_keygrips) @@ -3157,9 +3164,10 @@ agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, parm.key = key; parm.keylen = keylen; - snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s", + snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s%s", *timestamparg? timestamparg : "", unattended? " --unattended":"", + mode1003? " --mode1003":"", force? " --force":"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); diff --git a/g10/call-agent.h b/g10/call-agent.h index 282ba066b..7b4e4abda 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -230,7 +230,7 @@ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen); /* Send a key to the agent. */ -gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, +gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, int mode1003, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force, u32 *keyid, u32 *mainkeyid, int pubkey_algo, diff --git a/g10/import.c b/g10/import.c index ebfd73805..9affe057c 100644 --- a/g10/import.c +++ b/g10/import.c @@ -2853,7 +2853,31 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, continue; } - err = build_classic_transfer_sexp (pk, &tmpsexp); + tmpsexp = NULL; + if (ski->s2k.mode == 1003) + { + const void *tmpbuf; + unsigned int tmpbuflen; + int npkey; + + /* Fixme: Check that the public key parameters in pkey match + * those in the s-expression of the secret key. */ + npkey = pubkey_get_npkey (pk->pubkey_algo); + if (npkey+1 > PUBKEY_MAX_NSKEY) + err = gpg_error (GPG_ERR_BAD_SECKEY); + else if (!pk->pkey[npkey] + || !gcry_mpi_get_flag (pk->pkey[npkey], GCRYMPI_FLAG_OPAQUE)) + err = gpg_error (GPG_ERR_BAD_SECKEY); + else + { + tmpbuf = gcry_mpi_get_opaque (pk->pkey[npkey], &tmpbuflen); + tmpbuflen = (tmpbuflen +7)/8; /* Fixup bits to bytes */ + err = gcry_sexp_new (&tmpsexp, tmpbuf, tmpbuflen, 0); + } + } + else + err = build_classic_transfer_sexp (pk, &tmpsexp); + xfree (transferkey); transferkey = NULL; if (!err) @@ -2883,7 +2907,8 @@ transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, /* Send the wrapped key to the agent. */ { char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1); - err = agent_import_key (ctrl, desc, &cache_nonce, + err = agent_import_key (ctrl, desc, ski->s2k.mode == 1003, + &cache_nonce, wrappedkey, wrappedkeylen, batch, force, pk->keyid, pk->main_keyid, pk->pubkey_algo, pk->timestamp); |
