diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 31195c4ce..c0b608a4a 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -17,7 +17,18 @@ * along with this program; if not, see . */ -/* Only v2 of the ssh-agent protocol is implemented. */ +/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs + are: + + RFC-4250 - Protocol Assigned Numbers + RFC-4251 - Protocol Architecture + RFC-4252 - Authentication Protocol + RFC-4253 - Transport Layer Protocol + RFC-5656 - ECC support + + The protocol for the agent is defined in OpenSSH's PROTOCL.agent + file. + */ #include @@ -61,6 +72,7 @@ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) +#define SPEC_FLAG_IS_ECDSA (1 << 1) /* The name of the control file. */ #define SSH_CONTROL_FILE_NAME "sshcontrol" @@ -99,6 +111,10 @@ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, estream_t request, estream_t response); + +struct ssh_key_type_spec; +typedef struct ssh_key_type_spec ssh_key_type_spec_t; + /* Type, which is used for associating request handlers with the appropriate request IDs. */ typedef struct ssh_request_spec @@ -119,12 +135,13 @@ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, /* The encoding of a generated signature is dependent on the algorithm; therefore algorithm specific signature encoding functions are necessary. */ -typedef gpg_error_t (*ssh_signature_encoder_t) (estream_t signature_blob, +typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ -typedef struct ssh_key_type_spec +struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; @@ -157,9 +174,16 @@ typedef struct ssh_key_type_spec algorithm. */ ssh_signature_encoder_t signature_encoder; + /* The name of the ECC curve or NULL. */ + const char *curve_name; + + /* The hash algorithm to be used with this key. 0 for using the + default. */ + int hash_algo; + /* Misc flags. */ unsigned int flags; -} ssh_key_type_spec_t; +}; /* An object used to access the sshcontrol file. */ @@ -204,10 +228,15 @@ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t response); static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); -static gpg_error_t ssh_signature_encoder_rsa (estream_t signature_blob, +static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); -static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob, +static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); +static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_mpi_t *mpis); @@ -240,13 +269,29 @@ static ssh_key_type_spec_t ssh_key_types[] = { "ssh-rsa", "rsa", "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - SPEC_FLAG_USE_PKCS1V2 + NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "dsa", "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - 0 + NULL, 0, 0 }, + { + "ecdsa-sha2-nistp256", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp384", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp521", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA + } + }; @@ -341,6 +386,7 @@ stream_write_byte (estream_t stream, unsigned char b) return err; } + /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) @@ -431,8 +477,9 @@ stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) } /* Read a binary string from STREAM into STRING, store size of string - in STRING_SIZE; depending on SECURE use secure memory for - string. */ + in STRING_SIZE. Append a hidden nul so that the result may + directly be used as a C string. Depending on SECURE use secure + memory for STRING. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) @@ -1114,13 +1161,16 @@ ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) /* Signature encoder function for RSA. */ static gpg_error_t -ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis) +ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char *data; size_t data_n; gpg_error_t err; gcry_mpi_t s; + (void)spec; + s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); @@ -1138,7 +1188,8 @@ ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis) /* Signature encoder function for DSA. */ static gpg_error_t -ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) +ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data; @@ -1146,8 +1197,12 @@ ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) gpg_error_t err; int i; + (void)spec; + data = NULL; + /* FIXME: Why this complicated code? Why collecting boths mpis in a + buffer instead of writing them out one after the other? */ for (i = 0; i < 2; i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); @@ -1180,23 +1235,63 @@ ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) return err; } + +/* Signature encoder function for ECDSA. */ +static gpg_error_t +ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, + estream_t stream, gcry_mpi_t *mpis) +{ + unsigned char *data[2] = {NULL, NULL}; + size_t data_n[2]; + size_t innerlen; + gpg_error_t err; + int i; + + innerlen = 0; + for (i = 0; i < DIM(data); i++) + { + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]); + if (err) + goto out; + innerlen += 4 + data_n[i]; + } + + err = stream_write_uint32 (stream, innerlen); + if (err) + goto out; + + for (i = 0; i < DIM(data); i++) + { + err = stream_write_string (stream, data[i], data_n[i]); + if (err) + goto out; + } + + out: + for (i = 0; i < DIM(data); i++) + xfree (data[i]); + return err; +} + + /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified - by the KEY_SPEC, SECRET, MPIS and COMMENT, which is to be stored in - *SEXP. Returns usual error code. */ + by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to + be stored at R_SEXP. Returns an error code. */ static gpg_error_t sexp_key_construct (gcry_sexp_t *r_sexp, ssh_key_type_spec_t key_spec, int secret, - gcry_mpi_t *mpis, const char *comment) + const char *curve_name, gcry_mpi_t *mpis, + const char *comment) { const char *key_identifier[] = { "public-key", "private-key" }; gpg_error_t err; gcry_sexp_t sexp_new = NULL; - char *formatbuf = NULL; + void *formatbuf = NULL; void **arg_list = NULL; int arg_idx; estream_t format; @@ -1219,7 +1314,7 @@ sexp_key_construct (gcry_sexp_t *r_sexp, /* Key identifier, algorithm identifier, mpis, comment, and a NULL as a safeguard. */ - arg_list = xtrymalloc (sizeof (*arg_list) * (2 + elems_n + 1 + 1)); + arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1)); if (!arg_list) { err = gpg_error_from_syserror (); @@ -1230,6 +1325,11 @@ sexp_key_construct (gcry_sexp_t *r_sexp, es_fputs ("(%s(%s", format); arg_list[arg_idx++] = &key_identifier[secret]; arg_list[arg_idx++] = &key_spec.identifier; + if (curve_name) + { + es_fputs ("(curve%s)", format); + arg_list[arg_idx++] = &curve_name; + } for (i = 0; i < elems_n; i++) { @@ -1261,7 +1361,6 @@ sexp_key_construct (gcry_sexp_t *r_sexp, } format = NULL; - log_debug ("sexp formatbuf='%s' nargs=%d\n", formatbuf, arg_idx); err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); if (err) goto out; @@ -1281,34 +1380,28 @@ sexp_key_construct (gcry_sexp_t *r_sexp, /* This functions breaks up the key contained in the S-Expression SEXP according to KEY_SPEC. The MPIs are bundled in a newly create list, which is to be stored in MPIS; a newly allocated string - holding the comment will be stored in COMMENT; SECRET will be - filled with a boolean flag specifying what kind of key it is. - Returns usual error code. */ + holding the curve name may be stored at RCURVE, and a comment will + be stored at COMMENT; SECRET will be filled with a boolean flag + specifying what kind of key it is. Returns an error code. */ static gpg_error_t sexp_key_extract (gcry_sexp_t sexp, ssh_key_type_spec_t key_spec, int *secret, - gcry_mpi_t **mpis, char **comment) + gcry_mpi_t **mpis, char **r_curve, char **comment) { - gpg_error_t err; - gcry_sexp_t value_list; - gcry_sexp_t value_pair; - gcry_sexp_t comment_list; + gpg_error_t err = 0; + gcry_sexp_t value_list = NULL; + gcry_sexp_t value_pair = NULL; + gcry_sexp_t comment_list = NULL; unsigned int i; - char *comment_new; + char *comment_new = NULL; const char *data; size_t data_n; int is_secret; size_t elems_n; const char *elems; - gcry_mpi_t *mpis_new; + gcry_mpi_t *mpis_new = NULL; gcry_mpi_t mpi; - - err = 0; - value_list = NULL; - value_pair = NULL; - comment_list = NULL; - comment_new = NULL; - mpis_new = NULL; + char *curve_name = NULL; data = gcry_sexp_nth_data (sexp, 0, &data_n); if (! data) @@ -1374,6 +1467,51 @@ sexp_key_extract (gcry_sexp_t sexp, if (err) goto out; + if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* Parse the "curve" parameter. We currently expect the curve + name for ECC and not the parameters of the curve. This can + easily be changed but then we need to find the curve name + from the parameters using gcry_pk_get_curve. */ + const char *mapped; + + value_pair = gcry_sexp_find_token (value_list, "curve", 5); + if (!value_pair) + { + err = gpg_error (GPG_ERR_INV_CURVE); + goto out; + } + curve_name = gcry_sexp_nth_string (value_pair, 1); + if (!curve_name) + { + err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ + goto out; + } + + /* Fixme: The mapping should be done by using gcry_pk_get_curve + et al to iterate over all name aliases. */ + if (!strcmp (curve_name, "NIST P-256")) + mapped = "nistp256"; + else if (!strcmp (curve_name, "NIST P-384")) + mapped = "nistp384"; + else if (!strcmp (curve_name, "NIST P-521")) + mapped = "nistp521"; + else + mapped = NULL; + if (mapped) + { + xfree (curve_name); + curve_name = xtrystrdup (mapped); + if (!curve_name) + { + err = gpg_error_from_syserror (); + goto out; + } + } + gcry_sexp_release (value_pair); + value_pair = NULL; + } + /* We do not require a comment sublist to be present here. */ data = NULL; data_n = 0; @@ -1398,6 +1536,7 @@ sexp_key_extract (gcry_sexp_t sexp, *secret = is_secret; *mpis = mpis_new; *comment = comment_new; + *r_curve = curve_name; out: @@ -1407,6 +1546,7 @@ sexp_key_extract (gcry_sexp_t sexp, if (err) { + xfree (curve_name); xfree (comment_new); mpint_list_free (mpis_new); } @@ -1493,6 +1633,24 @@ ssh_key_type_lookup (const char *ssh_name, const char *name, return err; } + +/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns + NULL if not found. */ +static const char * +ssh_identifier_from_curve_name (const char *curve_name) +{ + int i; + + for (i = 0; i < DIM (ssh_key_types); i++) + if (ssh_key_types[i].curve_name + && !strcmp (ssh_key_types[i].curve_name, curve_name)) + return ssh_key_types[i].ssh_identifier; + + return NULL; +} + + + /* Receive a key from STREAM, according to the key specification given as KEY_SPEC. Depending on SECRET, receive a secret or a public key. If READ_COMMENT is true, receive a comment string as well. @@ -1509,6 +1667,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; + char *curve_name = NULL; + err = stream_read_cstring (stream, &key_type); if (err) @@ -1518,6 +1678,50 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, if (err) goto out; + if ((spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* The format of an ECDSA key is: + * string key_type ("ecdsa-sha2-nistp256" | + * "ecdsa-sha2-nistp384" | + * "ecdsa-sha2-nistp521" ) + * string ecdsa_curve_name + * string ecdsa_public_key + * mpint ecdsa_private + * + * Note that we use the mpint reader instead of the string + * reader for ecsa_public_key. + */ + unsigned char *buffer; + const char *mapped; + + err = stream_read_string (stream, 0, &buffer, NULL); + if (err) + goto out; + curve_name = buffer; + /* Fixme: Check that curve_name matches the keytype. */ + /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" + curve names, we need to translate them here to Libgcrypt's + native names. */ + if (!strcmp (curve_name, "nistp256")) + mapped = "NIST P-256"; + else if (!strcmp (curve_name, "nistp384")) + mapped = "NIST P-384"; + else if (!strcmp (curve_name, "nistp521")) + mapped = "NIST P-521"; + else + mapped = NULL; + if (mapped) + { + xfree (curve_name); + curve_name = xtrystrdup (mapped); + if (!curve_name) + { + err = gpg_error_from_syserror (); + goto out; + } + } + } + err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list); if (err) goto out; @@ -1541,7 +1745,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, goto out; } - err = sexp_key_construct (&key, spec, secret, mpi_list, comment? comment:""); + err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, + comment? comment:""); if (err) goto out; @@ -1550,8 +1755,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, *key_new = key; out: - mpint_list_free (mpi_list); + xfree (curve_name); xfree (key_type); xfree (comment); @@ -1563,7 +1768,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, BLOB/BLOB_SIZE. Returns zero on success or an error code. */ static gpg_error_t ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, - const char *type, gcry_mpi_t *mpis) + ssh_key_type_spec_t *spec, + const char *curve_name, gcry_mpi_t *mpis) { unsigned char *blob_new; long int blob_size_new; @@ -1585,14 +1791,31 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, goto out; } - err = stream_write_cstring (stream, type); - if (err) - goto out; + if ((spec->flags & SPEC_FLAG_IS_ECDSA) && curve_name) + { + const char *sshname = ssh_identifier_from_curve_name (curve_name); + if (!curve_name) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto out; + } + err = stream_write_cstring (stream, sshname); + if (err) + goto out; + err = stream_write_cstring (stream, curve_name); + if (err) + goto out; + } + else + { + err = stream_write_cstring (stream, spec->ssh_identifier); + if (err) + goto out; + } - for (i = 0; mpis[i] && (! err); i++) - err = stream_write_mpi (stream, mpis[i]); - if (err) - goto out; + for (i = 0; mpis[i]; i++) + if ((err = stream_write_mpi (stream, mpis[i]))) + goto out; blob_size_new = es_ftell (stream); if (blob_size_new == -1) @@ -1634,22 +1857,19 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, OVERRIDE_COMMENT is not NULL, it will be used instead of the comment stored in the key. */ static gpg_error_t -ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, +ssh_send_key_public (estream_t stream, + gcry_sexp_t key_public, const char *override_comment) { ssh_key_type_spec_t spec; - gcry_mpi_t *mpi_list; - char *key_type; - char *comment; - unsigned char *blob; + gcry_mpi_t *mpi_list = NULL; + char *key_type = NULL; + char *curve; + char *comment = NULL; + unsigned char *blob = NULL; size_t blob_n; gpg_error_t err; - key_type = NULL; - mpi_list = NULL; - comment = NULL; - blob = NULL; - err = sexp_extract_identifier (key_public, &key_type); if (err) goto out; @@ -1658,12 +1878,11 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, if (err) goto out; - err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &comment); + err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &curve, &comment); if (err) goto out; - err = ssh_convert_key_to_blob (&blob, &blob_n, - spec.ssh_identifier, mpi_list); + err = ssh_convert_key_to_blob (&blob, &blob_n, &spec, curve, mpi_list); if (err) goto out; @@ -1677,8 +1896,9 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, out: mpint_list_free (mpi_list); - xfree (key_type); + xfree (curve); xfree (comment); + xfree (key_type); xfree (blob); return err; @@ -1731,7 +1951,10 @@ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) - return gpg_error (GPG_ERR_INTERNAL); + { + gpg_error_t err = gcry_pk_testkey (key); + return err? err : gpg_error (GPG_ERR_INTERNAL); + } return 0; } @@ -1744,6 +1967,7 @@ static gpg_error_t key_secret_to_public (gcry_sexp_t *key_public, ssh_key_type_spec_t spec, gcry_sexp_t key_secret) { + char *curve; char *comment; gcry_mpi_t *mpis; gpg_error_t err; @@ -1752,16 +1976,18 @@ key_secret_to_public (gcry_sexp_t *key_public, comment = NULL; mpis = NULL; - err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, &comment); + err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, + &curve, &comment); if (err) goto out; - err = sexp_key_construct (key_public, spec, 0, mpis, comment); + err = sexp_key_construct (key_public, spec, 0, curve, mpis, comment); out: mpint_list_free (mpis); xfree (comment); + xfree (curve); return err; } @@ -2134,7 +2360,7 @@ data_hash (unsigned char *data, size_t data_n, signature in newly allocated memory in SIG and it's size in SIG_N; SIG_ENCODER is the signature encoder to use. */ static gpg_error_t -data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, +data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, unsigned char **sig, size_t *sig_n) { gpg_error_t err; @@ -2145,10 +2371,6 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, gcry_mpi_t sig_value = NULL; unsigned char *sig_blob = NULL; size_t sig_blob_n = 0; - char *identifier = NULL; - const char *identifier_raw; - size_t identifier_n; - ssh_key_type_spec_t spec; int ret; unsigned int i; const char *elems; @@ -2227,29 +2449,11 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, goto out; } - identifier_raw = gcry_sexp_nth_data (valuelist, 0, &identifier_n); - if (! identifier_raw) - { - err = gpg_error (GPG_ERR_INV_SEXP); - goto out; - } - - identifier = make_cstring (identifier_raw, identifier_n); - if (! identifier) - { - err = gpg_error_from_syserror (); - goto out; - } - - err = ssh_key_type_lookup (NULL, identifier, &spec); + err = stream_write_cstring (stream, spec->ssh_identifier); if (err) goto out; - err = stream_write_cstring (stream, spec.ssh_identifier); - if (err) - goto out; - - elems = spec.elems_signature; + elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); @@ -2261,7 +2465,7 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, for (i = 0; i < elems_n; i++) { - sublist = gcry_sexp_find_token (valuelist, spec.elems_signature + i, 1); + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (! sublist) { err = gpg_error (GPG_ERR_INV_SEXP); @@ -2282,7 +2486,7 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, if (err) goto out; - err = (*sig_encoder) (stream, mpis); + err = spec->signature_encoder (spec, stream, mpis); if (err) goto out; @@ -2325,7 +2529,6 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, gcry_sexp_release (signature_sexp); gcry_sexp_release (sublist); mpint_list_free (mpis); - xfree (identifier); return err; } @@ -2348,6 +2551,7 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) u32 flags; gpg_error_t err; gpg_error_t ret_err; + int hash_algo; key_blob = NULL; data = NULL; @@ -2374,14 +2578,18 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) if (err) goto out; + hash_algo = spec.hash_algo; + if (!hash_algo) + hash_algo = GCRY_MD_SHA1; /* Use the default. */ + /* Hash data. */ - hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1); + hash_n = gcry_md_get_algo_dlen (hash_algo); if (! hash_n) { err = gpg_error (GPG_ERR_INTERNAL); goto out; } - err = data_hash (data, data_size, GCRY_MD_SHA1, hash); + err = data_hash (data, data_size, hash_algo, hash); if (err) goto out; @@ -2392,14 +2600,17 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) /* Sign data. */ - ctrl->digest.algo = GCRY_MD_SHA1; + ctrl->digest.algo = hash_algo; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; - ctrl->digest.raw_value = ! (spec.flags & SPEC_FLAG_USE_PKCS1V2); + if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) + ctrl->digest.raw_value = 0; + else + ctrl->digest.raw_value = 1; ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); - err = data_sign (ctrl, spec.signature_encoder, &sig, &sig_n); + err = data_sign (ctrl, &spec, &sig, &sig_n); out: @@ -2520,6 +2731,7 @@ reenter_compare_cb (struct pin_entry_info_s *pi) return -1; } + /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in @@ -2570,7 +2782,6 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm) goto out; } - pi = gcry_calloc_secure (2, sizeof (*pi) + 100 + 1); if (!pi) { diff --git a/agent/protect.c b/agent/protect.c index 68e1a049f..d26573d11 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -51,13 +51,14 @@ static struct { const char *algo; const char *parmlist; int prot_from, prot_to; + int ecc_hack; } protect_info[] = { { "rsa", "nedpqu", 2, 5 }, { "dsa", "pqgyx", 4, 4 }, { "elg", "pgyx", 3, 3 }, - { "ecdsa","pabgnqd", 6, 6 }, - { "ecdh", "pabgnqd", 6, 6 }, - { "ecc", "pabgnqd", 6, 6 }, + { "ecdsa","pabgnqd", 6, 6, 1 }, + { "ecdh", "pabgnqd", 6, 6, 1 }, + { "ecc", "pabgnqd", 6, 6, 1 }, { NULL } }; @@ -450,6 +451,8 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned long s2k_count) { int rc; + const char *parmlist; + int prot_from_idx, prot_to_idx; const unsigned char *s; const unsigned char *hash_begin, *hash_end; const unsigned char *prot_begin, *prot_end, *real_end; @@ -494,10 +497,13 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + parmlist = protect_info[infidx].parmlist; + prot_from_idx = protect_info[infidx].prot_from; + prot_to_idx = protect_info[infidx].prot_to; prot_begin = prot_end = NULL; - for (i=0; (c=protect_info[infidx].parmlist[i]); i++) + for (i=0; (c=parmlist[i]); i++) { - if (i == protect_info[infidx].prot_from) + if (i == prot_from_idx) prot_begin = s; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); @@ -507,7 +513,20 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n != 1 || c != *s) - return gpg_error (GPG_ERR_INV_SEXP); + { + if (n == 5 && !memcmp (s, "curve", 5) + && !i && protect_info[infidx].ecc_hack) + { + /* This is a private ECC key but the first parameter is + the name of the curve. We change the parameter list + here to the one we expect in this case. */ + parmlist = "?qd"; + prot_from_idx = 2; + prot_to_idx = 2; + } + else + return gpg_error (GPG_ERR_INV_SEXP); + } s += n; n = snext (&s); if (!n) @@ -516,7 +535,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; - if (i == protect_info[infidx].prot_to) + if (i == prot_to_idx) prot_end = s; s++; } @@ -533,7 +552,6 @@ agent_protect (const unsigned char *plainkey, const char *passphrase, assert (!depth); real_end = s-1; - /* Hash the stuff. Because the timestamp_exp won't get protected, we can't simply hash a continuous buffer but need to use several md_writes. */ diff --git a/agent/t-protect.c b/agent/t-protect.c index 67a60a897..02b614a38 100644 --- a/agent/t-protect.c +++ b/agent/t-protect.c @@ -138,6 +138,24 @@ test_agent_protect (void) "\x55\x87\x11\x1C\x74\xE7\x7F\xA0\xBA\xE3\x34\x5D\x61\xBF\x29\x29\x29\x00" }; + struct key_spec key_ecdsa_valid = + { + "\x28\x31\x31\x3A\x70\x72\x69\x76\x61\x74\x65\x2D\x6B\x65\x79\x28" + "\x35\x3A\x65\x63\x64\x73\x61\x28\x35\x3A\x63\x75\x72\x76\x65\x31" + "\x30\x3A\x4E\x49\x53\x54\x20\x50\x2D\x32\x35\x36\x29\x28\x31\x3A" + "\x71\x36\x35\x3A\x04\x64\x5A\x12\x6F\x86\x7C\x43\x87\x2B\x7C\xAF" + "\x77\xFE\xD8\x22\x31\xEA\xE6\x89\x9F\xAA\xEA\x63\x26\xBC\x49\xED" + "\x85\xC6\xD2\xC9\x8B\x38\xD2\x78\x75\xE6\x1C\x27\x57\x01\xC5\xA1" + "\xE3\xF9\x1F\xBE\xCF\xC1\x72\x73\xFE\xA4\x58\xB6\x6A\x92\x7D\x33" + "\x1D\x02\xC9\xCB\x12\x29\x28\x31\x3A\x64\x33\x33\x3A\x00\x81\x2D" + "\x69\x9A\x5F\x5B\x6F\x2C\x99\x61\x36\x15\x6B\x44\xD8\x06\xC1\x54" + "\xC1\x4C\xFB\x70\x6A\xB6\x64\x81\x78\xF3\x94\x2F\x30\x5D\x29\x29" + "\x28\x37\x3A\x63\x6F\x6D\x6D\x65\x6E\x74\x32\x32\x3A\x2F\x68\x6F" + "\x6D\x65\x2F\x77\x6B\x2F\x2E\x73\x73\x68\x2F\x69\x64\x5F\x65\x63" + "\x64\x73\x61\x29\x29" + }; + + struct { const char *key; @@ -167,6 +185,9 @@ test_agent_protect (void) { key_rsa_bogus_1.string, "passphrase", 0, 0, NULL, 0, GPG_ERR_INV_SEXP, NULL, 0 }, + { key_ecdsa_valid.string, + "passphrase", 0, 0, NULL, 0, 0, NULL, 0 }, + /* FIXME: add more test data. */ }; @@ -177,8 +198,8 @@ test_agent_protect (void) &specs[i].result, &specs[i].resultlen, 0); if (gpg_err_code (ret) != specs[i].ret_expected) { - printf ("agent_protect() returned '%i/%s'; expected '%i/%s'\n", - ret, gpg_strerror (ret), + printf ("agent_protect(%d) returned '%i/%s'; expected '%i/%s'\n", + i, ret, gpg_strerror (ret), specs[i].ret_expected, gpg_strerror (specs[i].ret_expected)); abort (); } diff --git a/common/ssh-utils.c b/common/ssh-utils.c index dc0ca26c9..0c7156746 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -97,6 +97,34 @@ get_fingerprint (gcry_sexp_t key, void **r_fpr, size_t *r_len, int as_string) elems = "pqgy"; gcry_md_write (md, "\0\0\0\x07ssh-dss", 11); break; + case GCRY_PK_ECDSA: + /* We only support the 3 standard curves for now. It is just a + quick hack. */ + elems = "q"; + gcry_md_write (md, "\0\0\0\x13" "ecdsa-sha2-nistp", 20); + l2 = gcry_sexp_find_token (list, "curve", 0); + if (!l2) + elems = ""; + else + { + gcry_free (name); + name = gcry_sexp_nth_string (l2, 1); + gcry_sexp_release (l2); + l2 = NULL; + if (!name) + elems = ""; + else if (!strcmp (name, "NIST P-256") || !strcmp (name, "nistp256")) + gcry_md_write (md, "256\0\0\0\x08nistp256", 15); + else if (!strcmp (name, "NIST P-384") || !strcmp (name, "nistp384")) + gcry_md_write (md, "384\0\0\0\x08nistp521", 15); + else if (!strcmp (name, "NIST P-521") || !strcmp (name, "nistp521")) + gcry_md_write (md, "521\0\0\0\x08nistp521", 15); + else + elems = ""; + } + if (!*elems) + err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_CURVE); + break; default: elems = ""; err = gpg_err_make (default_errsource, GPG_ERR_PUBKEY_ALGO);