1
0
mirror of https://github.com/libssh2/libssh2.git synced 2025-11-20 02:42:09 +03:00

Add support for ECDSA keys and host keys (#41)

This commit lands full ECDSA key support when using the OpenSSL
backend. Which includes:

New KEX methods:
ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521

Can now read OpenSSL formatted ECDSA key files.

Now supports known host keys of type ecdsa-sha2-nistp256.

New curve types:
NID_X9_62_prime256v1, NID_secp384r1, NID_secp521r1

Default host key preferred ordering is now nistp256, nistp384,
nistp521, rsa, dss.

Ref: https://github.com/libssh2/libssh2/issues/41

Closes https://github.com/libssh2/libssh2/pull/206
This commit is contained in:
Will Cosgrove
2017-08-31 14:57:40 -07:00
committed by Viktor Szakats
parent bcd492163b
commit aba34f5f56
13 changed files with 2087 additions and 110 deletions

View File

@@ -49,6 +49,24 @@
#define EVP_MAX_BLOCK_LENGTH 32
#endif
static unsigned char *
write_bn(unsigned char *buf, const BIGNUM *bn, int bn_bytes)
{
unsigned char *p = buf;
/* Left space for bn size which will be written below. */
p += 4;
*p = 0;
BN_bn2bin(bn, p + 1);
if (!(*(p + 1) & 0x80)) {
memmove(p, p + 1, --bn_bytes);
}
_libssh2_htonu32(p - 4, bn_bytes); /* Post write bn size. */
return p + bn_bytes;
}
int
_libssh2_rsa_new(libssh2_rsa_ctx ** rsa,
const unsigned char *edata,
@@ -231,6 +249,144 @@ _libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx,
}
#endif /* LIBSSH_DSA */
#if LIBSSH2_ECDSA
/* _libssh2_ecdsa_key_get_curve_type
*
* returns key curve type that maps to libssh2_curve_type
*
*/
libssh2_curve_type
_libssh2_ecdsa_key_get_curve_type(_libssh2_ec_key *key)
{
const EC_GROUP *group = EC_KEY_get0_group(key);
return EC_GROUP_get_curve_name(group);
}
/* _libssh2_ecdsa_curve_type_from_name
*
* returns 0 for success, key curve type that maps to libssh2_curve_type
*
*/
int
_libssh2_ecdsa_curve_type_from_name(const char *name, libssh2_curve_type *out_type)
{
int ret = 0;
libssh2_curve_type type;
if ( name == NULL || strlen(name) != 19 )
return -1;
if ( strcmp(name, "ecdsa-sha2-nistp256") == 0)
type = LIBSSH2_EC_CURVE_NISTP256;
else if ( strcmp(name, "ecdsa-sha2-nistp384") == 0)
type = LIBSSH2_EC_CURVE_NISTP384;
else if ( strcmp(name, "ecdsa-sha2-nistp521") == 0)
type = LIBSSH2_EC_CURVE_NISTP521;
else {
ret = -1;
}
if (ret == 0 && out_type) {
*out_type = type;
}
return ret;
}
/* _libssh2_ecdsa_curve_name_with_octal_new
*
* Creates a new public key given an octal string, length and type
*
*/
int
_libssh2_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx ** ec_ctx,
const unsigned char *k,
size_t k_len, libssh2_curve_type curve)
{
int ret = 0;
const EC_GROUP *ec_group = NULL;
EC_KEY *ec_key = EC_KEY_new_by_curve_name(curve);
EC_POINT *point = NULL;
if ( ec_key ) {
ec_group = EC_KEY_get0_group(ec_key);
point = EC_POINT_new(ec_group);
ret = EC_POINT_oct2point(ec_group, point, k, k_len, NULL);
ret = EC_KEY_set_public_key(ec_key, point);
if (point != NULL)
EC_POINT_free(point);
if ( ec_ctx != NULL )
*ec_ctx = ec_key;
}
return (ret == 1) ? 0 : -1;
}
#define LIBSSH2_ECDSA_VERIFY(digest_type) \
{ \
unsigned char hash[SHA##digest_type##_DIGEST_LENGTH]; \
libssh2_sha##digest_type(m, m_len, hash); \
ret = ECDSA_do_verify(hash, SHA##digest_type##_DIGEST_LENGTH, \
ecdsa_sig, ec_key); \
\
}
int
_libssh2_ecdsa_verify(libssh2_ecdsa_ctx * ctx,
const unsigned char *r, size_t r_len,
const unsigned char *s, size_t s_len,
const unsigned char *m, size_t m_len)
{
int ret = 0;
EC_KEY *ec_key = (EC_KEY*)ctx;
libssh2_curve_type type = _libssh2_ecdsa_key_get_curve_type(ec_key);
#if HAVE_OPAQUE_STRUCTS
ECDSA_SIG *ecdsa_sig = ECDSA_SIG_new();
BIGNUM *pr = BN_new();
BIGNUM *ps = BN_new();
BN_bin2bn(r, r_len, pr);
BN_bin2bn(s, s_len, ps);
ECDSA_SIG_set0(ecdsa_sig, pr, ps);
#else
ECDSA_SIG ecdsa_sig_;
ECDSA_SIG *ecdsa_sig = &ecdsa_sig_;
ecdsa_sig_.r = BN_new();
BN_bin2bn(r, r_len, ecdsa_sig_.r);
ecdsa_sig_.s = BN_new();
BN_bin2bn(s, s_len, ecdsa_sig_.s);
#endif
if ( type == LIBSSH2_EC_CURVE_NISTP256 ) {
LIBSSH2_ECDSA_VERIFY(256);
}else if ( type == LIBSSH2_EC_CURVE_NISTP384 ) {
LIBSSH2_ECDSA_VERIFY(384);
}else if ( type == LIBSSH2_EC_CURVE_NISTP521 ) {
LIBSSH2_ECDSA_VERIFY(512);
}
#if HAVE_OPAQUE_STRUCTS
if ( ecdsa_sig )
ECDSA_SIG_free(ecdsa_sig);
#else
BN_clear_free(ecdsa_sig_.s);
BN_clear_free(ecdsa_sig_.r);
#endif
return (ret == 1) ? 0 : -1;
}
#endif /* LIBSSH2_ECDSA */
int
_libssh2_cipher_init(_libssh2_cipher_ctx * h,
_libssh2_cipher_type(algo),
@@ -599,6 +755,41 @@ _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa,
}
#endif /* LIBSSH_DSA */
#if LIBSSH2_ECDSA
int
_libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx,
LIBSSH2_SESSION * session,
const char *filedata, size_t filedata_len,
unsigned const char *passphrase)
{
pem_read_bio_func read_ec =
(pem_read_bio_func) &PEM_read_bio_ECPrivateKey;
(void) session;
_libssh2_init_if_needed();
return read_private_key_from_memory((void **) ec_ctx, read_ec,
filedata, filedata_len, passphrase);
}
int
_libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx,
LIBSSH2_SESSION * session,
const char *filename, unsigned const char *passphrase)
{
pem_read_bio_func read_ec = (pem_read_bio_func) &PEM_read_bio_ECPrivateKey;
(void) session;
_libssh2_init_if_needed ();
return read_private_key_from_file((void **) ec_ctx, read_ec,
filename, passphrase);
}
#endif /* LIBSSH2_ECDSA */
int
_libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session,
libssh2_rsa_ctx * rsactx,
@@ -675,6 +866,69 @@ _libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx,
}
#endif /* LIBSSH_DSA */
#if LIBSSH2_ECDSA
int
_libssh2_ecdsa_sign(LIBSSH2_SESSION * session, libssh2_ecdsa_ctx * ec_ctx,
const unsigned char *hash, unsigned long hash_len,
unsigned char **signature, size_t *signature_len)
{
int r_len, s_len;
int rc = 0;
size_t out_buffer_len = 0;
unsigned char *sp;
const BIGNUM *pr = NULL, *ps = NULL;
unsigned char *temp_buffer = NULL;
unsigned char *out_buffer = NULL;
ECDSA_SIG *sig = ECDSA_do_sign(hash, hash_len, ec_ctx);
if ( sig == NULL )
return -1;
#if HAVE_OPAQUE_STRUCTS
ECDSA_SIG_get0(sig, &pr, &ps);
#else
pr = sig->r;
ps = sig->s;
#endif
r_len = BN_num_bytes(pr) + 1;
s_len = BN_num_bytes(ps) + 1;
temp_buffer = malloc(r_len + s_len + 8);
if ( temp_buffer == NULL ) {
rc = -1;
goto clean_exit;
}
sp = temp_buffer;
sp = write_bn(sp, pr, r_len);
sp = write_bn(sp, ps, s_len);
out_buffer_len = (size_t)(sp - temp_buffer);
out_buffer = LIBSSH2_CALLOC(session, out_buffer_len);
if ( out_buffer == NULL ) {
rc = -1;
goto clean_exit;
}
memcpy(out_buffer, temp_buffer, out_buffer_len);
*signature = out_buffer;
*signature_len = out_buffer_len;
clean_exit:
if ( temp_buffer != NULL )
free(temp_buffer);
if ( sig )
ECDSA_SIG_free(sig);
return rc;
}
#endif /* LIBSSH2_ECDSA */
int
_libssh2_sha1_init(libssh2_sha1_ctx *ctx)
{
@@ -779,6 +1033,110 @@ _libssh2_sha256(const unsigned char *message, unsigned long len,
return 1; /* error */
}
int
_libssh2_sha384_init(libssh2_sha384_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
*ctx = EVP_MD_CTX_new();
if (*ctx == NULL)
return 0;
if (EVP_DigestInit(*ctx, EVP_get_digestbyname("sha384")))
return 1;
EVP_MD_CTX_free(*ctx);
*ctx = NULL;
return 0;
#else
EVP_MD_CTX_init(ctx);
return EVP_DigestInit(ctx, EVP_get_digestbyname("sha384"));
#endif
}
int
_libssh2_sha384(const unsigned char *message, unsigned long len,
unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
EVP_MD_CTX * ctx = EVP_MD_CTX_new();
if (ctx == NULL)
return 1; /* error */
if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha384"))) {
EVP_DigestUpdate(ctx, message, len);
EVP_DigestFinal(ctx, out, NULL);
EVP_MD_CTX_free(ctx);
return 0; /* success */
}
EVP_MD_CTX_free(ctx);
#else
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha384"))) {
EVP_DigestUpdate(&ctx, message, len);
EVP_DigestFinal(&ctx, out, NULL);
return 0; /* success */
}
#endif
return 1; /* error */
}
int
_libssh2_sha512_init(libssh2_sha512_ctx *ctx)
{
#ifdef HAVE_OPAQUE_STRUCTS
*ctx = EVP_MD_CTX_new();
if (*ctx == NULL)
return 0;
if (EVP_DigestInit(*ctx, EVP_get_digestbyname("sha512")))
return 1;
EVP_MD_CTX_free(*ctx);
*ctx = NULL;
return 0;
#else
EVP_MD_CTX_init(ctx);
return EVP_DigestInit(ctx, EVP_get_digestbyname("sha512"));
#endif
}
int
_libssh2_sha512(const unsigned char *message, unsigned long len,
unsigned char *out)
{
#ifdef HAVE_OPAQUE_STRUCTS
EVP_MD_CTX * ctx = EVP_MD_CTX_new();
if (ctx == NULL)
return 1; /* error */
if(EVP_DigestInit(ctx, EVP_get_digestbyname("sha512"))) {
EVP_DigestUpdate(ctx, message, len);
EVP_DigestFinal(ctx, out, NULL);
EVP_MD_CTX_free(ctx);
return 0; /* success */
}
EVP_MD_CTX_free(ctx);
#else
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
if(EVP_DigestInit(&ctx, EVP_get_digestbyname("sha512"))) {
EVP_DigestUpdate(&ctx, message, len);
EVP_DigestFinal(&ctx, out, NULL);
return 0; /* success */
}
#endif
return 1; /* error */
}
int
_libssh2_md5_init(libssh2_md5_ctx *ctx)
{
@@ -801,24 +1159,6 @@ _libssh2_md5_init(libssh2_md5_ctx *ctx)
#endif
}
static unsigned char *
write_bn(unsigned char *buf, const BIGNUM *bn, int bn_bytes)
{
unsigned char *p = buf;
/* Left space for bn size which will be written below. */
p += 4;
*p = 0;
BN_bn2bin(bn, p + 1);
if (!(*(p + 1) & 0x80)) {
memmove(p, p + 1, --bn_bytes);
}
_libssh2_htonu32(p - 4, bn_bytes); /* Post write bn size. */
return p + bn_bytes;
}
static unsigned char *
gen_publickey_from_rsa(LIBSSH2_SESSION *session, RSA *rsa,
size_t *key_len)
@@ -1029,6 +1369,272 @@ gen_publickey_from_dsa_evp(LIBSSH2_SESSION *session,
}
#endif /* LIBSSH_DSA */
#if LIBSSH2_ECDSA
static int
gen_publickey_from_ec_evp(LIBSSH2_SESSION *session,
unsigned char **method,
size_t *method_len,
unsigned char **pubkeydata,
size_t *pubkeydata_len,
EVP_PKEY *pk)
{
int rc = 0;
EC_KEY *ec = NULL;
unsigned char *p;
unsigned char* method_buf = NULL;
unsigned char *key;
size_t key_len = 0;
unsigned char *octal_value = NULL;
size_t octal_len;
const EC_POINT *public_key;
const EC_GROUP *group;
BN_CTX *bn_ctx;
libssh2_curve_type type;
_libssh2_debug(session,
LIBSSH2_TRACE_AUTH,
"Computing public key from EC private key envelop");
bn_ctx = BN_CTX_new();
if ( bn_ctx == NULL )
return -1;
ec = EVP_PKEY_get1_EC_KEY(pk);
if ( ec == NULL ) {
rc = -1;
goto clean_exit;
}
public_key = EC_KEY_get0_public_key(ec);
group = EC_KEY_get0_group(ec);
type = _libssh2_ecdsa_key_get_curve_type(ec);
method_buf = LIBSSH2_ALLOC(session, 19);
if (method_buf == NULL) {
return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"out of memory");
}
if ( type == LIBSSH2_EC_CURVE_NISTP256 )
memcpy(method_buf, "ecdsa-sha2-nistp256", 19);
else if ( type == LIBSSH2_EC_CURVE_NISTP384 )
memcpy(method_buf, "ecdsa-sha2-nistp384", 19);
else if ( type == LIBSSH2_EC_CURVE_NISTP521 )
memcpy(method_buf, "ecdsa-sha2-nistp521", 19);
else {
_libssh2_debug(session,
LIBSSH2_TRACE_ERROR,
"Unsupported EC private key type");
rc = -1;
goto clean_exit;
}
/* get length */
octal_len = EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx);
if (octal_len > EC_MAX_POINT_LEN) {
rc = -1;
goto clean_exit;
}
octal_value = malloc(octal_len);
if ( octal_value == NULL ) {
rc = -1;
goto clean_exit;
}
/* convert to octal */
if (EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED,
octal_value, octal_len, bn_ctx) != octal_len) {
rc = -1;
goto clean_exit;
}
/* Key form is: type_len(4) + type(19) + domain_len(4) + domain(8) + pub_key_len(4) + pub_key(~65). */
key_len = 4 + 19 + 4 + 8 + 4 + octal_len;
key = LIBSSH2_ALLOC(session, key_len);
if (key == NULL) {
rc = -1;
goto clean_exit;
}
/* Process key encoding. */
p = key;
/* Key type */
_libssh2_store_str(&p, (const char*)method_buf, 19);
/* Name domain */
_libssh2_store_str(&p, (const char*)method_buf + 11, 8);
/* Public key */
_libssh2_store_str(&p, (const char*)octal_value, octal_len);
*method = method_buf;
*method_len = 19;
*pubkeydata = key;
*pubkeydata_len = key_len;
clean_exit:
if ( ec != NULL)
EC_KEY_free(ec);
if (bn_ctx != NULL) {
BN_CTX_free(bn_ctx);
}
if ( octal_value != NULL )
free(octal_value);
if ( rc == 0 )
return 0;
if (method_buf != NULL )
LIBSSH2_FREE(session, method_buf);
return -1;
}
/*
* _libssh2_ecdsa_create_key
*
* Creates a local private key based on input curve
* and returns octal value and octal length
*
*/
int
_libssh2_ecdsa_create_key(_libssh2_ec_key **out_private_key,
unsigned char **out_public_key_octal,
size_t *out_public_key_octal_len, libssh2_curve_type curve_type)
{
int ret = 1;
size_t octal_len = 0;
unsigned char octal_value[EC_MAX_POINT_LEN];
const EC_POINT *public_key = NULL;
EC_KEY *private_key = NULL;
const EC_GROUP *group = NULL;
/* create key */
BN_CTX *bn_ctx = BN_CTX_new();
if (!bn_ctx)
return -1;
private_key = EC_KEY_new_by_curve_name(curve_type);
group = EC_KEY_get0_group(private_key);
EC_KEY_generate_key(private_key);
public_key = EC_KEY_get0_public_key(private_key);
/* get length */
octal_len = EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx);
if (octal_len > EC_MAX_POINT_LEN) {
ret = -1;
goto clean_exit;
}
/* convert to octal */
if (EC_POINT_point2oct(group, public_key, POINT_CONVERSION_UNCOMPRESSED,
octal_value, octal_len, bn_ctx) != octal_len){
ret = -1;
goto clean_exit;
}
if (out_private_key != NULL)
*out_private_key = private_key;
if (out_public_key_octal) {
*out_public_key_octal = malloc(octal_len);
if (out_public_key_octal == NULL) {
ret = -1;
goto clean_exit;
}
memcpy(*out_public_key_octal, octal_value, octal_len);
}
if (out_public_key_octal_len != NULL)
*out_public_key_octal_len = octal_len;
clean_exit:
if (bn_ctx)
BN_CTX_free(bn_ctx);
return (ret == 1) ? 0 : -1;
}
/* _libssh2_ecdh_gen_k
*
* Computes the shared secret K given a local private key,
* remote public key and length
*/
int
_libssh2_ecdh_gen_k(_libssh2_bn **k, _libssh2_ec_key *private_key,
const unsigned char *server_public_key, size_t server_public_key_len)
{
int ret = 0;
int rc;
size_t secret_len;
unsigned char *secret;
const EC_GROUP *private_key_group;
EC_POINT *server_public_key_point;
BN_CTX *bn_ctx = BN_CTX_new();
if ( !bn_ctx )
return -1;
if ( k == NULL )
return -1;
private_key_group = EC_KEY_get0_group(private_key);
server_public_key_point = EC_POINT_new(private_key_group);
if ( server_public_key_point == NULL )
return -1;
rc = EC_POINT_oct2point(private_key_group, server_public_key_point, server_public_key, server_public_key_len, bn_ctx);
if ( rc != 1 ) {
ret = -1;
goto clean_exit;
}
secret_len = (EC_GROUP_get_degree(private_key_group) + 7) / 8;
secret = malloc(secret_len);
if (!secret) {
ret = -1;
goto clean_exit;
}
secret_len = ECDH_compute_key(secret, secret_len, server_public_key_point, private_key, NULL);
if( secret_len <= 0 || secret_len > EC_MAX_POINT_LEN ) {
ret = -1;
goto clean_exit;
}
BN_bin2bn(secret, secret_len, *k);
clean_exit:
if ( server_public_key_point != NULL )
EC_POINT_free(server_public_key_point);
if ( bn_ctx != NULL )
BN_CTX_free(bn_ctx);
if ( secret != NULL )
free(secret);
return ret;
}
#endif /* LIBSSH2_ECDSA */
int
_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session,
unsigned char **method,
@@ -1088,6 +1694,13 @@ _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session,
break;
#endif /* LIBSSH_DSA */
#if LIBSSH2_ECDSA
case EVP_PKEY_EC :
st = gen_publickey_from_ec_evp(
session, method, method_len, pubkeydata, pubkeydata_len, pk);
break;
#endif
default :
st = _libssh2_error(session,
LIBSSH2_ERROR_FILE,