diff --git a/agent/agent.h b/agent/agent.h index 8f68b2619..e72a75068 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -433,7 +433,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, gcry_sexp_t *result, - char **r_passphrase); + char **r_passphrase, + uint64_t *r_timestamp); gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); gpg_error_t agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip, diff --git a/agent/command.c b/agent/command.c index cd3ab18f7..4a4d6e81a 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1941,7 +1941,7 @@ cmd_passwd (assuan_context_t ctx, char *line) opt_verify? NULL : cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, - &s_skey, &passphrase); + &s_skey, &passphrase, NULL); if (err) ; else if (shadow_info) @@ -2522,7 +2522,7 @@ cmd_export_key (assuan_context_t ctx, char *line) err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, - openpgp ? &passphrase : NULL); + openpgp ? &passphrase : NULL, NULL); if (err) goto leave; if (shadow_info) @@ -2667,28 +2667,30 @@ cmd_delete_key (assuan_context_t ctx, char *line) -#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG -#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" -#else -#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" -#endif - static const char hlp_keytocard[] = - "KEYTOCARD [--force] \n" - "\n"; + "KEYTOCARD [--force] [ []]\n" + "\n" + "TIMESTAMP is required for OpenPGP and defaults to the Epoch.\n" + "ECDH are the hexified ECDH parameters for OpenPGP.\n" + "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; + char *argv[5]; + int argc; unsigned char grip[20]; + const char *serialno, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; - const char *serialno, *timestamp_str, *id; unsigned char *shadow_info = NULL; - time_t timestamp; + uint64_t timestamp; + char *ecdh_params = NULL; + unsigned int ecdh_params_len; + unsigned int extralen1, extralen2; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); @@ -2696,7 +2698,14 @@ cmd_keytocard (assuan_context_t ctx, char *line) force = has_option (line, "--force"); line = skip_options (line); - err = parse_keygrip (ctx, line, grip); + argc = split_fields (line, argv, DIM (argv)); + if (argc < 3) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + goto leave; + } + + err = parse_keygrip (ctx, argv[0], grip); if (err) goto leave; @@ -2706,82 +2715,112 @@ cmd_keytocard (assuan_context_t ctx, char *line) goto leave; } - /* Fixme: Replace the parsing code by split_fields(). */ - line += 40; - while (*line && (*line == ' ' || *line == '\t')) - line++; - serialno = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - id = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (!*line) - { - err = gpg_error (GPG_ERR_MISSING_VALUE); - goto leave; - } - *line = '\0'; - line++; - while (*line && (*line == ' ' || *line == '\t')) - line++; - timestamp_str = line; - while (*line && (*line != ' ' && *line != '\t')) - line++; - if (*line) - *line = '\0'; + /* Note that checking of the s/n is currently not implemented but we + * want to provide a clean interface if we ever implement it. */ + serialno = argv[1]; + if (!strcmp (serialno, "-")) + serialno = NULL; - if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) + keyref = argv[2]; + + err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, + &s_skey, NULL, ×tamp); + if (err) + goto leave; + + if (shadow_info) + { + /* Key is already on a smartcard - wer can't extract it. */ + err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); + goto leave; + } + + /* Default to the creation time as stored in the private key. The + * parameter is here so that gpg can make sure that the timestamp is + * used. It is also important for OpenPGP cards to allow computing + * of the fingerprint. Same goes for the ECDH params. */ + if (argc > 3) + { + timestamp = isotime2epoch_u64 (argv[3]); + if (argc > 4) + { + size_t n; + + err = parse_hexstring (ctx, argv[4], &n); + if (err) + goto leave; /* Badly formatted ecdh params. */ + n /= 2; + if (n < 4) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "ecdh param too short"); + goto leave; + } + ecdh_params_len = n; + ecdh_params = xtrymalloc (ecdh_params_len); + if (!ecdh_params) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (hex2bin (argv[4], ecdh_params, ecdh_params_len) < 0) + { + err = set_error (GPG_ERR_BUG, "hex2bin"); + goto leave; + } + } + } + else if (timestamp == (uint64_t)(-1)) + timestamp = isotime2epoch_u64 ("19700101T000000"); + + if (timestamp == (uint64_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } - err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, - &shadow_info, CACHE_MODE_IGNORE, NULL, - &s_skey, NULL); - if (err) - { - xfree (shadow_info); - goto leave; - } - if (shadow_info) - { - /* Key is on a smartcard already. */ - xfree (shadow_info); - gcry_sexp_release (s_skey); - err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); - goto leave; - } - - keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); - keydata = xtrymalloc_secure (keydatalen + 30); + /* Note: We can't use make_canon_sexp because we need to allocate a + * few extra bytes for our hack below. The 20 for extralen2 + * accounts for the sexp length of ecdh_params. */ + keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + extralen1 = 30; + extralen2 = ecdh_params? (20+20+ecdh_params_len) : 0; + keydata = xtrymalloc_secure (keydatalen + extralen1 + extralen2); if (keydata == NULL) { err = gpg_error_from_syserror (); - gcry_sexp_release (s_skey); goto leave; } - gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); + s_skey = NULL; + keydatalen--; /* Decrement for last '\0'. */ - /* Add timestamp "created-at" in the private key */ - snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); + + /* Hack to insert the timestamp "created-at" into the private key. */ + snprintf (keydata+keydatalen-1, extralen1, "(10:created-at10:%010llu))", + (unsigned long long)timestamp); keydatalen += 10 + 19 - 1; - err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); + + /* Hack to insert the timestamp "ecdh-params" into the private key. */ + if (ecdh_params) + { + snprintf (keydata+keydatalen-1, extralen2, "(11:ecdh-params%u:", + ecdh_params_len); + keydatalen += strlen (keydata+keydatalen-1) -1; + memcpy (keydata+keydatalen, ecdh_params, ecdh_params_len); + keydatalen += ecdh_params_len; + memcpy (keydata+keydatalen, "))", 3); + keydatalen += 2; + } + + err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: + xfree (ecdh_params); + gcry_sexp_release (s_skey); + xfree (shadow_info); return leave_cmd (ctx, err); } diff --git a/agent/findkey.c b/agent/findkey.c index 7917d2981..a359c7e1b 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -889,20 +889,24 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, - gcry_sexp_t *result, char **r_passphrase) + gcry_sexp_t *result, char **r_passphrase, + uint64_t *r_timestamp) { gpg_error_t err; unsigned char *buf; size_t len, buflen, erroff; gcry_sexp_t s_skey; + nvc_t keymeta = NULL; *result = NULL; if (shadow_info) *shadow_info = NULL; if (r_passphrase) *r_passphrase = NULL; + if (r_timestamp) + *r_timestamp = (uint64_t)(-1); - err = read_key_file (grip, &s_skey, NULL); + err = read_key_file (grip, &s_skey, &keymeta); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -915,7 +919,19 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, now. */ err = make_canon_sexp (s_skey, &buf, &len); if (err) - return err; + { + nvc_release (keymeta); + return err; + } + + if (r_timestamp && keymeta) + { + const char *created = nvc_get_string (keymeta, "Created:"); + + if (created) + *r_timestamp = isotime2epoch_u64 (created); + } + nvc_release (keymeta); switch (agent_private_key_type (buf)) { diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 6aed96b4f..ccd395dc6 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -69,7 +69,7 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, } rc = agent_key_from_file (ctrl, NULL, desc_text, ctrl->keygrip, &shadow_info, - CACHE_MODE_NORMAL, NULL, &s_skey, NULL); + CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY) diff --git a/agent/pksign.c b/agent/pksign.c index 09d61b8c7..571541dc8 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -311,7 +311,7 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip, &shadow_info, cache_mode, lookup_ttl, - &s_skey, NULL); + &s_skey, NULL, NULL); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_SECKEY)