Commit a788bd4f authored by Jakub Jelen's avatar Jakub Jelen Committed by Daiki Ueno

Support ECDSA in ssh-agent interface

https://bugzilla.gnome.org/show_bug.cgi?id=641082
parent b0a2020b
......@@ -28,4 +28,5 @@ gkd_ssh_agent_standalone_LDADD = \
libgkd-ssh-agent.la \
libegg-buffer.la \
libegg-secure.la \
libgkm.la \
$(DAEMON_LIBS)
......@@ -112,6 +112,11 @@ build_like_attributes (GckAttributes *attrs, CK_OBJECT_CLASS klass)
copy_attribute (attrs, CKA_VALUE, &builder);
break;
case CKK_EC:
copy_attribute (attrs, CKA_EC_PARAMS, &builder);
copy_attribute (attrs, CKA_EC_POINT, &builder);
break;
default:
g_return_val_if_reached (NULL);
break;
......@@ -306,7 +311,8 @@ load_identity_v2_attributes (GckObject *object, gpointer user_data)
attrs = gck_object_get (object, NULL, &error, CKA_ID, CKA_LABEL, CKA_KEY_TYPE, CKA_MODULUS,
CKA_PUBLIC_EXPONENT, CKA_PRIME, CKA_SUBPRIME, CKA_BASE,
CKA_VALUE, CKA_CLASS, CKA_MODULUS_BITS, CKA_TOKEN, GCK_INVALID);
CKA_VALUE, CKA_CLASS, CKA_MODULUS_BITS, CKA_TOKEN,
CKA_EC_POINT, CKA_EC_PARAMS, GCK_INVALID);
if (error) {
g_warning ("error retrieving attributes for public key: %s", egg_error_message (error));
g_clear_error (&error);
......@@ -643,6 +649,9 @@ op_add_identity (GkdSshAgentCall *call)
case CKK_DSA:
ret = gkd_ssh_agent_proto_read_pair_dsa (call->req, &offset, &priv, &pub);
break;
case CKK_EC:
ret = gkd_ssh_agent_proto_read_pair_ecdsa (call->req, &offset, &priv, &pub);
break;
default:
g_assert_not_reached ();
return FALSE;
......@@ -982,6 +991,7 @@ unlock_and_sign (GckSession *session, GckObject *key, gulong mech_type, const gu
return gck_session_sign (session, key, mech_type, input, n_input, n_result, NULL, err);
}
static gboolean
op_sign_request (GkdSshAgentCall *call)
{
......@@ -1002,6 +1012,7 @@ op_sign_request (GkdSshAgentCall *call)
gulong algo, mech;
GChecksumType halgo;
gsize n_hash = 0;
GQuark oid = 0;
offset = 5;
......@@ -1022,7 +1033,12 @@ op_sign_request (GkdSshAgentCall *call)
mech = CKM_RSA_PKCS;
else if (algo == CKK_DSA)
mech = CKM_DSA;
else
else if (algo == CKK_EC) {
mech = CKM_ECDSA;
oid = gkd_ssh_agent_proto_find_curve_oid (attrs);
if (!oid)
return FALSE;
} else
g_return_val_if_reached (FALSE);
if (!egg_buffer_get_byte_array (call->req, offset, &offset, &data, &n_data) ||
......@@ -1040,11 +1056,20 @@ op_sign_request (GkdSshAgentCall *call)
return TRUE;
}
/* Usually we hash the data with SHA1 */
if (flags & GKD_SSH_FLAG_OLD_SIGNATURE)
if (mech == CKM_ECDSA) {
/* ECDSA is using SHA-2 hash algorithms based on key size */
gint ret = gkd_ssh_agent_proto_curve_oid_to_hash_algo (oid);
if (ret == -1) {
egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
return FALSE;
}
halgo = (GChecksumType) ret;
} else if (flags & GKD_SSH_FLAG_OLD_SIGNATURE) {
halgo = G_CHECKSUM_MD5;
else
} else {
/* Usually we hash the data with SHA1 */
halgo = G_CHECKSUM_SHA1;
}
/* Build the hash */
if (mech == CKM_RSA_PKCS)
......@@ -1076,7 +1101,7 @@ op_sign_request (GkdSshAgentCall *call)
blobpos = call->resp->len;
egg_buffer_add_uint32 (call->resp, 0);
salgo = gkd_ssh_agent_proto_algo_to_keytype (algo);
salgo = gkd_ssh_agent_proto_algo_to_keytype (algo, oid);
g_assert (salgo);
egg_buffer_add_string (call->resp, salgo);
......@@ -1089,6 +1114,10 @@ op_sign_request (GkdSshAgentCall *call)
ret = gkd_ssh_agent_proto_write_signature_dsa (call->resp, result, n_result);
break;
case CKK_EC:
ret = gkd_ssh_agent_proto_write_signature_ecdsa (call->resp, result, n_result);
break;
default:
g_assert_not_reached ();
}
......
......@@ -103,7 +103,18 @@ void gkd_ssh_agent_checkin_main_session (GckSession*
gulong gkd_ssh_agent_proto_keytype_to_algo (const gchar *salgo);
const gchar* gkd_ssh_agent_proto_algo_to_keytype (gulong algo);
const gchar* gkd_ssh_agent_proto_algo_to_keytype (gulong algo,
GQuark oid);
GQuark gkd_ssh_agent_proto_curve_to_oid (const gchar *salgo);
const gchar* gkd_ssh_agent_proto_oid_to_curve (GQuark oid);
gint gkd_ssh_agent_proto_curve_oid_to_hash_algo (GQuark oid);
const gchar* gkd_ssh_agent_proto_curve_oid_to_keytype (GQuark oid);
GQuark gkd_ssh_agent_proto_find_curve_oid (GckAttributes *attrs);
gboolean gkd_ssh_agent_proto_read_mpi (EggBuffer *req,
gsize *offset,
......@@ -119,12 +130,24 @@ const guchar* gkd_ssh_agent_proto_read_challenge_v1 (EggBuffer *
gsize *offset,
gsize *n_challenge);
gboolean gkd_ssh_agent_proto_read_string_to_der (EggBuffer *req,
gsize *offset,
GckBuilder *attrs,
CK_ATTRIBUTE_TYPE type);
gboolean gkd_ssh_agent_proto_read_ecdsa_curve (EggBuffer *req,
gsize *offset,
GckBuilder *attrs);
gboolean gkd_ssh_agent_proto_write_mpi (EggBuffer *resp,
const GckAttribute *attr);
gboolean gkd_ssh_agent_proto_write_mpi_v1 (EggBuffer *resp,
const GckAttribute *attr);
gboolean gkd_ssh_agent_proto_write_string (EggBuffer *resp,
const GckAttribute *attr);
gboolean gkd_ssh_agent_proto_read_public (EggBuffer *req,
gsize *offset,
GckBuilder *attrs,
......@@ -138,6 +161,10 @@ gboolean gkd_ssh_agent_proto_read_public_dsa (EggBuffer *
gsize *offset,
GckBuilder *attrs);
gboolean gkd_ssh_agent_proto_read_public_ecdsa (EggBuffer *req,
gsize *offset,
GckBuilder *attrs);
gboolean gkd_ssh_agent_proto_read_public_v1 (EggBuffer *req,
gsize *offset,
GckBuilder *attrs);
......@@ -152,6 +179,11 @@ gboolean gkd_ssh_agent_proto_read_pair_dsa (EggBuffer *
GckBuilder *priv,
GckBuilder *pub);
gboolean gkd_ssh_agent_proto_read_pair_ecdsa (EggBuffer *req,
gsize *offset,
GckBuilder *priv,
GckBuilder *pub);
gboolean gkd_ssh_agent_proto_read_pair_v1 (EggBuffer *req,
gsize *offset,
GckBuilder *priv,
......@@ -166,6 +198,9 @@ gboolean gkd_ssh_agent_proto_write_public_rsa (EggBuffer *
gboolean gkd_ssh_agent_proto_write_public_dsa (EggBuffer *resp,
GckAttributes *attrs);
gboolean gkd_ssh_agent_proto_write_public_ecdsa (EggBuffer *resp,
GckAttributes *attrs);
gboolean gkd_ssh_agent_proto_write_public_v1 (EggBuffer *resp,
GckAttributes *attrs);
......@@ -177,4 +212,8 @@ gboolean gkd_ssh_agent_proto_write_signature_dsa (EggBuffer *
CK_BYTE_PTR signature,
CK_ULONG n_signature);
gboolean gkd_ssh_agent_proto_write_signature_ecdsa (EggBuffer *resp,
CK_BYTE_PTR signature,
CK_ULONG n_signature);
#endif /*GKDSSHPRIVATE_H_*/
......@@ -24,6 +24,8 @@
#include "gkd-ssh-agent-private.h"
#include "gkm/gkm-data-der.h"
#include "egg/egg-buffer.h"
#include <gck/gck.h>
......@@ -32,6 +34,34 @@
#include <string.h>
/* -----------------------------------------------------------------------------
* QUARKS
*/
static GQuark OID_ANSI_SECP256R1;
static GQuark OID_ANSI_SECP384R1;
static GQuark OID_ANSI_SECP521R1;
static void
init_quarks (void)
{
static volatile gsize quarks_inited = 0;
if (g_once_init_enter (&quarks_inited)) {
#define QUARK(name, value) \
name = g_quark_from_static_string(value)
QUARK (OID_ANSI_SECP256R1, "1.2.840.10045.3.1.7");
QUARK (OID_ANSI_SECP384R1, "1.3.132.0.34");
QUARK (OID_ANSI_SECP521R1, "1.3.132.0.35");
#undef QUARK
g_once_init_leave (&quarks_inited, 1);
}
}
gulong
gkd_ssh_agent_proto_keytype_to_algo (const gchar *salgo)
{
......@@ -40,19 +70,122 @@ gkd_ssh_agent_proto_keytype_to_algo (const gchar *salgo)
return CKK_RSA;
else if (strcmp (salgo, "ssh-dss") == 0)
return CKK_DSA;
else if (strcmp (salgo, "ecdsa-sha2-nistp256") == 0 ||
strcmp (salgo, "ecdsa-sha2-nistp384") == 0 ||
strcmp (salgo, "ecdsa-sha2-nistp521") == 0)
return CKK_EC;
return G_MAXULONG;
}
GQuark
gkd_ssh_agent_proto_curve_to_oid (const gchar *salgo)
{
g_return_val_if_fail (salgo, 0);
init_quarks ();
if (g_str_equal (salgo, "nistp256"))
return OID_ANSI_SECP256R1;
else if (g_str_equal (salgo, "nistp384"))
return OID_ANSI_SECP384R1;
else if (g_str_equal (salgo, "nistp521"))
return OID_ANSI_SECP521R1;
return 0;
}
const gchar*
gkd_ssh_agent_proto_algo_to_keytype (gulong algo)
gkd_ssh_agent_proto_oid_to_curve (GQuark oid)
{
if (algo == CKK_RSA)
g_return_val_if_fail (oid, NULL);
init_quarks ();
if (oid == OID_ANSI_SECP256R1)
return "nistp256";
else if (oid == OID_ANSI_SECP384R1)
return "nistp384";
else if (oid == OID_ANSI_SECP521R1)
return "nistp521";
return NULL;
}
gint
gkd_ssh_agent_proto_curve_oid_to_hash_algo (GQuark oid)
{
g_return_val_if_fail (oid, -1);
init_quarks ();
/* from rfc5656 */
if (oid == OID_ANSI_SECP256R1)
return G_CHECKSUM_SHA256;
else if (oid == OID_ANSI_SECP384R1)
return G_CHECKSUM_SHA384;
else if (oid == OID_ANSI_SECP521R1)
return G_CHECKSUM_SHA512;
return -1;
}
const gchar*
gkd_ssh_agent_proto_curve_oid_to_keytype (GQuark oid)
{
g_return_val_if_fail (oid, NULL);
init_quarks ();
if (oid == OID_ANSI_SECP256R1)
return "ecdsa-sha2-nistp256";
else if (oid == OID_ANSI_SECP384R1)
return "ecdsa-sha2-nistp384";
else if (oid == OID_ANSI_SECP521R1)
return "ecdsa-sha2-nistp521";
return NULL;
}
const gchar*
gkd_ssh_agent_proto_algo_to_keytype (gulong algo, GQuark curve_oid)
{
if (algo == CKK_RSA) {
g_return_val_if_fail (curve_oid == 0, NULL);
return "ssh-rsa";
else if (algo == CKK_DSA)
} else if (algo == CKK_DSA) {
g_return_val_if_fail (curve_oid == 0, NULL);
return "ssh-dss";
} else if (algo == CKK_EC) {
g_return_val_if_fail (curve_oid != 0, NULL);
return gkd_ssh_agent_proto_curve_oid_to_keytype (curve_oid);
}
return NULL;
}
GQuark
gkd_ssh_agent_proto_find_curve_oid (GckAttributes *attrs)
{
GBytes *bytes;
const GckAttribute *attr;
GQuark oid;
g_assert (attrs);
attr = gck_attributes_find (attrs, CKA_EC_PARAMS);
if (attr == NULL)
g_return_val_if_reached (0);
bytes = g_bytes_new (attr->value, attr->length);
oid = gkm_data_der_oid_from_ec_params (bytes);
g_bytes_unref (bytes);
return oid;
}
gboolean
gkd_ssh_agent_proto_read_mpi (EggBuffer *req, gsize *offset,
GckBuilder *builder,
......@@ -103,6 +236,27 @@ gkd_ssh_agent_proto_read_mpi_v1 (EggBuffer *req,
return TRUE;
}
gboolean
gkd_ssh_agent_proto_read_string_to_der (EggBuffer *req,
gsize *offset,
GckBuilder *attrs,
CK_ATTRIBUTE_TYPE type)
{
const guchar *data, *q_data;
gsize len, q_len;
GBytes *bytes;
if (!egg_buffer_get_byte_array (req, *offset, offset, &data, &len))
return FALSE;
bytes = gkm_data_der_encode_ecdsa_q_str (data, len);
q_data = g_bytes_get_data (bytes, &q_len);
gck_builder_add_data (attrs, type, q_data, q_len);
return TRUE;
}
gboolean
gkd_ssh_agent_proto_write_mpi (EggBuffer *resp,
const GckAttribute *attr)
......@@ -147,6 +301,23 @@ gkd_ssh_agent_proto_write_mpi_v1 (EggBuffer *resp,
return TRUE;
}
gboolean
gkd_ssh_agent_proto_write_string (EggBuffer *resp,
const GckAttribute *attr)
{
guchar *data;
g_assert (resp);
g_assert (attr);
data = egg_buffer_add_byte_array_empty (resp, attr->length);
if (data == NULL)
return FALSE;
memcpy (data, attr->value, attr->length);
return TRUE;
}
const guchar*
gkd_ssh_agent_proto_read_challenge_v1 (EggBuffer *req, gsize *offset, gsize *n_challenge)
{
......@@ -204,6 +375,9 @@ gkd_ssh_agent_proto_read_public (EggBuffer *req,
case CKK_DSA:
ret = gkd_ssh_agent_proto_read_public_dsa (req, offset, attrs);
break;
case CKK_EC:
ret = gkd_ssh_agent_proto_read_public_ecdsa (req, offset, attrs);
break;
default:
g_assert_not_reached ();
return FALSE;
......@@ -395,11 +569,99 @@ gkd_ssh_agent_proto_read_public_dsa (EggBuffer *req,
return TRUE;
}
gboolean
gkd_ssh_agent_proto_read_ecdsa_curve (EggBuffer *req,
gsize *offset,
GckBuilder *attrs)
{
GBytes *params;
gchar *curve_name;
const guchar *params_data;
GQuark oid;
gsize params_len;
g_assert (req);
g_assert (offset);
g_assert (attrs);
/* first part is the curve name (nistp* part of key name) and needs
* to be converted to CKA_EC_PARAMS
*/
if (!egg_buffer_get_string (req, *offset, offset, &curve_name,
(EggBufferAllocator)g_realloc))
return FALSE;
oid = gkd_ssh_agent_proto_curve_to_oid (curve_name);
g_return_val_if_fail (oid, FALSE);
params = gkm_data_der_get_ec_params (oid);
g_return_val_if_fail (params != NULL, FALSE);
params_data = g_bytes_get_data (params, &params_len);
gck_builder_add_data (attrs, CKA_EC_PARAMS, params_data, params_len);
return TRUE;
}
gboolean
gkd_ssh_agent_proto_read_pair_ecdsa (EggBuffer *req,
gsize *offset,
GckBuilder *priv_attrs,
GckBuilder *pub_attrs)
{
const GckAttribute *attr;
g_assert (req);
g_assert (offset);
g_assert (priv_attrs);
g_assert (pub_attrs);
if (!gkd_ssh_agent_proto_read_ecdsa_curve (req, offset, priv_attrs) ||
!gkd_ssh_agent_proto_read_string_to_der (req, offset, priv_attrs, CKA_EC_POINT) ||
!gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_VALUE))
return FALSE;
/* Copy attributes to the public key */
attr = gck_builder_find (priv_attrs, CKA_EC_POINT);
gck_builder_add_attribute (pub_attrs, attr);
attr = gck_builder_find (priv_attrs, CKA_EC_PARAMS);
gck_builder_add_attribute (pub_attrs, attr);
/* Add in your basic other required attributes */
gck_builder_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
gck_builder_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_EC);
gck_builder_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
gck_builder_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_EC);
return TRUE;
}
gboolean
gkd_ssh_agent_proto_read_public_ecdsa (EggBuffer *req,
gsize *offset,
GckBuilder *attrs)
{
g_assert (req);
g_assert (offset);
g_assert (attrs);
if (!gkd_ssh_agent_proto_read_ecdsa_curve (req, offset, attrs) ||
!gkd_ssh_agent_proto_read_string_to_der (req, offset, attrs, CKA_EC_POINT))
return FALSE;
/* Add in your basic other required attributes */
gck_builder_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
gck_builder_add_ulong (attrs, CKA_KEY_TYPE, CKK_EC);
return TRUE;
}
gboolean
gkd_ssh_agent_proto_write_public (EggBuffer *resp, GckAttributes *attrs)
{
gboolean ret = FALSE;
const gchar *salgo;
GQuark oid = 0;
gulong algo;
g_assert (resp);
......@@ -407,8 +669,13 @@ gkd_ssh_agent_proto_write_public (EggBuffer *resp, GckAttributes *attrs)
if (!gck_attributes_find_ulong (attrs, CKA_KEY_TYPE, &algo))
g_return_val_if_reached (FALSE);
if (algo == CKK_EC) {
oid = gkd_ssh_agent_proto_find_curve_oid (attrs);
if (!oid)
return FALSE;
}
salgo = gkd_ssh_agent_proto_algo_to_keytype (algo);
salgo = gkd_ssh_agent_proto_algo_to_keytype (algo, oid);
g_assert (salgo);
egg_buffer_add_string (resp, salgo);
......@@ -421,6 +688,10 @@ gkd_ssh_agent_proto_write_public (EggBuffer *resp, GckAttributes *attrs)
ret = gkd_ssh_agent_proto_write_public_dsa (resp, attrs);
break;
case CKK_EC:
ret = gkd_ssh_agent_proto_write_public_ecdsa (resp, attrs);
break;
default:
g_return_val_if_reached (FALSE);
break;
......@@ -487,6 +758,55 @@ gkd_ssh_agent_proto_write_public_dsa (EggBuffer *resp, GckAttributes *attrs)
return TRUE;
}
gboolean
gkd_ssh_agent_proto_write_public_ecdsa (EggBuffer *resp, GckAttributes *attrs)
{
const GckAttribute *attr;
GQuark oid;
const gchar *curve;
guchar *data;
const guchar *q_data;
GBytes *bytes, *q;
gboolean rv;
gsize q_len;
g_assert (resp);
g_assert (attrs);
/* decode curve name from EC_PARAMS */
oid = gkd_ssh_agent_proto_find_curve_oid (attrs);
g_return_val_if_fail (oid, FALSE);
curve = gkd_ssh_agent_proto_oid_to_curve (oid);
g_return_val_if_fail (curve != NULL, FALSE);
data = egg_buffer_add_byte_array_empty (resp, strlen (curve));
if (data == NULL)
return FALSE;
memcpy (data, curve, strlen(curve));
/* decode DER-encoded value Q */
attr = gck_attributes_find (attrs, CKA_EC_POINT);
g_return_val_if_fail (attr, FALSE);
bytes = g_bytes_new_static (attr->value, attr->length);
rv = gkm_data_der_decode_ecdsa_q (bytes, &q);
g_return_val_if_fail (rv, FALSE);
g_bytes_unref (bytes);
q_data = g_bytes_get_data (q, &q_len);
data = egg_buffer_add_byte_array_empty (resp, q_len);
if (data == NULL)
return FALSE;
memcpy (data, q_data, q_len);
g_bytes_unref (q);
return TRUE;
}
gboolean
gkd_ssh_agent_proto_write_public_v1 (EggBuffer *resp, GckAttributes *attrs)
{
......@@ -532,3 +852,55 @@ gkd_ssh_agent_proto_write_signature_dsa (EggBuffer *resp, CK_BYTE_PTR signature,
g_return_val_if_fail (n_signature == 40, FALSE);
return egg_buffer_add_byte_array (resp, signature, n_signature);
}
static gboolean
gkd_ssh_agent_buffer_put_rfc_mpi (EggBuffer *buffer, const guchar *val,
gsize len)
{
gsize pad = 0;
/*
* From RFC 4251:
* If the most significant bit would be set for a positive number,
* the number MUST be preceded by a zero byte.
*/
if ((val[0] & 0x80))
pad = 1;
if (!egg_buffer_add_uint32 (buffer, len + pad))
return 0;
if (pad && !egg_buffer_add_byte (buffer, 0x00))
return 0;
return egg_buffer_append (buffer, val, len);
}
gboolean
gkd_ssh_agent_proto_write_signature_ecdsa (EggBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
{
gboolean rv;
gsize mpi_size;
gsize pads = 0;
g_return_val_if_fail ((n_signature % 2) == 0, FALSE);
/* PKCS#11 lists the MPIs concatenated, SSH-agent expects the size headers */
mpi_size = n_signature/2;
/*
* From RFC 4251, Section 5:
* If the most significant bit would be set for a positive number,
* the number MUST be preceded by a zero byte.
*/
pads = ((signature[0] & 0x80) == 0x80) + ((signature[mpi_size] & 0x80) == 0x80);
/* First we need header for the whole signature blob
* (including 2 length headers and potential "padding")
*/
egg_buffer_add_uint32 (resp, n_signature + 8 + pads);
rv = gkd_ssh_agent_buffer_put_rfc_mpi (resp, signature, mpi_size);
g_return_val_if_fail (rv, FALSE);
rv = gkd_ssh_agent_buffer_put_rfc_mpi (resp, signature + mpi_size, mpi_size);
return rv;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment