diff --git a/agent/agent.h b/agent/agent.h index 0f804cd8b..b7eacf471 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -414,7 +414,8 @@ void start_command_handler_ssh (ctrl_t, gnupg_fd_t); gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result); int agent_write_private_key (const unsigned char *grip, - const void *buffer, size_t length, int force); + const void *buffer, size_t length, int force, + const char *serialno, const char *keyref); 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 ebd28ab5a..727bb9b94 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3142,7 +3142,7 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, goto out; /* Store this key to our key storage. */ - err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); + err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, NULL, NULL); if (err) goto out; diff --git a/agent/command.c b/agent/command.c index 082ed4144..9bb6fa306 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2255,10 +2255,11 @@ cmd_import_key (assuan_context_t ctx, char *line) err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count, -1); if (!err) - err = agent_write_private_key (grip, finalkey, finalkeylen, force); + err = agent_write_private_key (grip, finalkey, finalkeylen, force, + NULL, NULL); } else - err = agent_write_private_key (grip, key, realkeylen, force); + err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL); leave: gcry_sexp_release (openpgp_sexp); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 06cd1c840..42052d48e 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -1067,7 +1067,8 @@ convert_from_openpgp_native (ctrl_t ctrl, if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, ctrl->s2k_count, -1)) - agent_write_private_key (grip, protectedkey, protectedkeylen, 1); + agent_write_private_key (grip, protectedkey, protectedkeylen, 1, + NULL, NULL); xfree (protectedkey); } else @@ -1076,7 +1077,7 @@ convert_from_openpgp_native (ctrl_t ctrl, agent_write_private_key (grip, *r_key, gcry_sexp_canon_len (*r_key, 0, NULL,NULL), - 1); + 1, NULL, NULL); } } diff --git a/agent/findkey.c b/agent/findkey.c index 89a18fa9e..157870e17 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -55,12 +55,14 @@ struct try_unprotect_arg_s /* Note: Ownership of FNAME and FP are moved to this function. */ static gpg_error_t write_extended_private_key (char *fname, estream_t fp, int update, - const void *buf, size_t len) + const void *buf, size_t len, + const char *serialno, const char *keyref) { gpg_error_t err; nvc_t pk = NULL; gcry_sexp_t key = NULL; int remove = 0; + char *token = NULL; if (update) { @@ -93,6 +95,37 @@ write_extended_private_key (char *fname, estream_t fp, int update, if (err) goto leave; + /* If requested write a Token line. */ + if (serialno && keyref) + { + nve_t item; + const char *s; + + token = strconcat (serialno, " ", keyref, NULL); + if (!token) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* fixme: the strcmp should compare only the first two strings. */ + for (item = nvc_lookup (pk, "Token:"); + item; + item = nve_next_value (item, "Token:")) + if ((s = nve_value (item)) && !strcmp (s, token)) + break; + if (!item) + { + /* No token or no token with that value exists. Add a new + * one so that keys which have been stored on several cards + * are well supported. */ + err = nvc_add (pk, "Token:", token); + if (err) + goto leave; + } + } + + err = es_fseek (fp, 0, SEEK_SET); if (err) goto leave; @@ -132,15 +165,18 @@ write_extended_private_key (char *fname, estream_t fp, int update, xfree (fname); gcry_sexp_release (key); nvc_release (pk); + xfree (token); return err; } /* Write an S-expression formatted key to our key storage. With FORCE - passed as true an existing key with the given GRIP will get - overwritten. */ + * passed as true an existing key with the given GRIP will get + * overwritten. If SERIALNO and KEYREF are give an a Token line is added to + * th key if the extended format ist used. */ int agent_write_private_key (const unsigned char *grip, - const void *buffer, size_t length, int force) + const void *buffer, size_t length, int force, + const char *serialno, const char *keyref) { char *fname; estream_t fp; @@ -208,17 +244,20 @@ agent_write_private_key (const unsigned char *grip, if (first != '(') { /* Key is already in the extended format. */ - return write_extended_private_key (fname, fp, 1, buffer, length); + return write_extended_private_key (fname, fp, 1, buffer, length, + serialno, keyref); } if (first == '(' && opt.enable_extended_key_format) { /* Key is in the old format - but we want the extended format. */ - return write_extended_private_key (fname, fp, 0, buffer, length); + return write_extended_private_key (fname, fp, 0, buffer, length, + serialno, keyref); } } if (opt.enable_extended_key_format) - return write_extended_private_key (fname, fp, 0, buffer, length); + return write_extended_private_key (fname, fp, 0, buffer, length, + serialno, keyref); if (es_fwrite (buffer, length, 1, fp) != 1) { @@ -1580,6 +1619,13 @@ agent_write_shadow_key (const unsigned char *grip, unsigned char *shdkey; size_t len; + /* Just in case some caller did not parse the stuff correctly, skip + * leading spaces. */ + while (spacep (serialno)) + serialno++; + while (spacep (keyid)) + keyid++; + shadow_info = make_shadow_info (serialno, keyid); if (!shadow_info) return gpg_error_from_syserror (); @@ -1593,7 +1639,7 @@ agent_write_shadow_key (const unsigned char *grip, } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); - err = agent_write_private_key (grip, shdkey, len, force); + err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid); 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 d5c80d0aa..84342f9ea 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -68,7 +68,7 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, buf = p; } - rc = agent_write_private_key (grip, buf, len, force); + rc = agent_write_private_key (grip, buf, len, force, NULL, NULL); xfree (buf); return rc; } diff --git a/agent/keyformat.txt b/agent/keyformat.txt index c7426db9d..058fb0143 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -18,7 +18,8 @@ hexadecimal representation of the keygrip[2] and suffixed with ".key". * Extended Private Key Format -GnuPG 2.3+ will use a new format to store private keys that is both +** Overview +GnuPG 2.3+ uses a new format to store private keys that is both more flexible and easier to read and edit by human beings. The new format stores name,value-pairs using the common mail and http header convention. Example (here indented with two spaces): @@ -28,6 +29,8 @@ convention. Example (here indented with two spaces): Use-for-ssh: yes OpenSSH-cert: long base64 encoded string wrapped so that this key file can be easily edited with a standard editor. + Token: D2760001240102000005000011730000 OPENPGP.1 + Token: FF020001008A77C1 PIV.9C Key: (shadowed-private-key (rsa (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900 @@ -52,33 +55,66 @@ Keys in the extended format can be recognized by looking at the first byte of the file. If it starts with a '(' it is a naked S-expression, otherwise it is a key in extended format. -** Names - +*** Names A name must start with a letter and end with a colon. Valid characters are all ASCII letters, numbers and the hyphen. Comparison of names is done case insensitively. Names may be used several times -to represent an array of values. - -The name "Key:" is special in that it may occur only once and the -associated value holds the actual S-expression with the cryptographic -key. The S-expression is formatted using the 'Advanced Format' -(GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that -the file can be easily inspected and edited. See section 'Private Key -Format' below for details. - -** Values +to represent an array of values. Note that the name "Key" is special +in that it is madandory must occur only once. +*** Values Values are UTF-8 encoded strings. Values can be wrapped at any point, and continued in the next line indicated by leading whitespace. A continuation line with one leading space does not introduce a blank so that the lines can be effectively concatenated. A blank line as part of a continuation line encodes a newline. -** Comments - +*** Comments Lines containing only whitespace, and lines starting with whitespace followed by '#' are considered to be comments and are ignored. +** Well defined names + +*** Description +This is a human readable string describing the key. + +*** Key +The name "Key" is special in that it is mandatory and must occur only +once. The associated value holds the actual S-expression with the +cryptographic key. The S-expression is formatted using the 'Advanced +Format' (GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters +so that the file can be easily inspected and edited. See section +'Private Key Format' below for details. + +*** Label +This is a short human readable description for the key which can be +used by the software to describe the key in a user interface. For +example as part of the description in a prompt for a PIN or +passphrase. It is often used instead of a comment element preent in +the S-expression of the "Key" item. + +*** OpenSSH-cert +This takes a base64 encoded string wrapped so that this +key file can be easily edited with a standard editor. Several of such +items can be used. + +*** Token +If such an item exists it overrides the info given by the "shadow" +parameter in the S-expression. Using this item makes it possible to +describe a key which is stored on several tokens and also makes it +easy to update this info using a standard editor. The syntax is the +same as with the "shadow" parameter: + +- Serialnumber of the token +- Key reference from the token in full format (e.g. "OpenPGP.2") +- An optional fixed length of the PIN. + +*** Use-for-ssh +If given and the value is "yes" or "1" the key is allowed for use by +gpg-agent's ssh-agent implementation. This is thus the same as +putting the keygrip into the 'sshcontrol' file. Only one such item +should exist. + * Private Key Format ** Unprotected Private Key Format diff --git a/agent/protect-tool.c b/agent/protect-tool.c index ec7b47695..30d78cd43 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -799,12 +799,15 @@ agent_askpin (ctrl_t ctrl, * to stdout. */ int agent_write_private_key (const unsigned char *grip, - const void *buffer, size_t length, int force) + const void *buffer, size_t length, int force, + const char *serialno, const char *keyref) { char hexgrip[40+4+1]; char *p; (void)force; + (void)serialno; + (void)keyref; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); diff --git a/agent/protect.c b/agent/protect.c index 61fb8f45d..c7fb773e1 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1667,7 +1667,8 @@ agent_get_shadow_info (const unsigned char *shadowkey, R_HEXSN and the Id string as a malloced string at R_IDSTR. On error an error code is returned and NULL is stored at the result parameters addresses. If the serial number or the ID string is not - required, NULL may be passed for them. */ + required, NULL may be passed for them. Note that R_PINLEN is + currently not used by any caller. */ gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen)