aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2025-11-16 16:49:52 +0000
committerWerner Koch <[email protected]>2025-11-16 16:57:14 +0000
commit47bab26daf035ffdce97e4957bdb6ad12dbea506 (patch)
treeefb6029844d8de1169d206730cee85af50fb2760
parentgpg: Change the mode1003 format for composite keys. (diff)
downloadgnupg-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.h2
-rw-r--r--agent/command-ssh.c2
-rw-r--r--agent/command.c154
-rw-r--r--agent/cvt-openpgp.c4
-rw-r--r--agent/divert-tpm2.c4
-rw-r--r--agent/findkey.c16
-rw-r--r--agent/genkey.c2
-rw-r--r--agent/protect-tool.c4
-rw-r--r--common/sexputil.c2
-rw-r--r--g10/call-agent.c12
-rw-r--r--g10/call-agent.h2
-rw-r--r--g10/import.c29
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);