mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
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.
This commit is contained in:
parent
f76a0312c3
commit
649b31c663
@ -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);
|
||||
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)
|
||||
{
|
||||
|
@ -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. */
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user