summaryrefslogtreecommitdiffstats
path: root/agent
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2012-12-12 18:47:21 +0100
committerWerner Koch <wk@gnupg.org>2012-12-12 18:47:21 +0100
commit649b31c663b8674bc874b4ef283d714a13dc8cfe (patch)
tree54c390283b48af81c4941dccfd5b3a67bd290ec5 /agent
parentssh: Rewrite a function for better maintainability (diff)
downloadgnupg2-649b31c663b8674bc874b4ef283d714a13dc8cfe.tar.xz
gnupg2-649b31c663b8674bc874b4ef283d714a13dc8cfe.zip
ssh: Support ECDSA keys.
* agent/command-ssh.c (SPEC_FLAG_IS_ECDSA): New. (struct ssh_key_type_spec): Add fields CURVE_NAME and HASH_ALGO. (ssh_key_types): Add types ecdsa-sha2-nistp{256,384,521}. (ssh_signature_encoder_t): Add arg spec and adjust all callers. (ssh_signature_encoder_ecdsa): New. (sexp_key_construct, sexp_key_extract, ssh_receive_key) (ssh_convert_key_to_blob): Support ecdsa. (ssh_identifier_from_curve_name): New. (ssh_send_key_public): Retrieve and pass the curve_name. (key_secret_to_public): Ditto. (data_sign): Add arg SPEC and change callers to pass it. (ssh_handler_sign_request): Get the hash algo from SPEC. * common/ssh-utils.c (get_fingerprint): Support ecdsa. * agent/protect.c (protect_info): Add flag ECC_HACK. (agent_protect): Allow the use of the "curve" parameter. * agent/t-protect.c (test_agent_protect): Add a test case for ecdsa. * agent/command-ssh.c (ssh_key_grip): Print a better error code. -- The 3 standard curves are now supported in gpg-agent's ssh-agent protocol implementation. I tested this with all 3 curves and keys generated by OpenSSH 5.9p1. Using existing non-ssh generated keys will likely fail for now. To fix this, the code should first undergo some more cleanup; then the fixes are pretty straightforward. And yes, the data structures are way too complicated.
Diffstat (limited to 'agent')
-rw-r--r--agent/command-ssh.c403
-rw-r--r--agent/protect.c34
-rw-r--r--agent/t-protect.c25
3 files changed, 356 insertions, 106 deletions
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 <http://www.gnu.org/licenses/>.
*/
-/* 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 <config.h>
@@ -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);
- if (err)
- goto out;
-
- err = stream_write_cstring (stream, spec.ssh_identifier);
+ 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 ();
}