diff --git a/include/libssh2.h b/include/libssh2.h index 6a9177a..85dcb6d 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -404,11 +404,12 @@ typedef struct _LIBSSH2_POLLFD { #define LIBSSH2_HOSTKEY_HASH_MD5 1 #define LIBSSH2_HOSTKEY_HASH_SHA1 2 #define LIBSSH2_HOSTKEY_HASH_SHA256 3 - + /* Hostkey Types */ -#define LIBSSH2_HOSTKEY_TYPE_UNKNOWN 0 -#define LIBSSH2_HOSTKEY_TYPE_RSA 1 -#define LIBSSH2_HOSTKEY_TYPE_DSS 2 +#define LIBSSH2_HOSTKEY_TYPE_UNKNOWN 0 +#define LIBSSH2_HOSTKEY_TYPE_RSA 1 +#define LIBSSH2_HOSTKEY_TYPE_DSS 2 +#define LIBSSH2_HOSTKEY_TYPE_ECDSA 3 /* Disconnect Codes (defined by SSH protocol) */ #define SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 @@ -967,6 +968,7 @@ libssh2_knownhost_init(LIBSSH2_SESSION *session); #define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) #define LIBSSH2_KNOWNHOST_KEY_SSHRSA (2<<18) #define LIBSSH2_KNOWNHOST_KEY_SSHDSS (3<<18) +#define LIBSSH2_KNOWNHOST_KEY_ECDSA (4<<18) #define LIBSSH2_KNOWNHOST_KEY_UNKNOWN (7<<18) LIBSSH2_API int diff --git a/src/crypto.h b/src/crypto.h index b15a122..76f5d57 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -122,6 +122,53 @@ int _libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, unsigned const char *passphrase); #endif +#if LIBSSH2_ECDSA +int +_libssh2_ecdsa_curve_name_with_octal_new(libssh2_ecdsa_ctx ** ecdsactx, + const unsigned char *k, + size_t k_len, libssh2_curve_type type); +int +_libssh2_ecdsa_new_private(libssh2_ecdsa_ctx ** ec_ctx, + LIBSSH2_SESSION * session, + const char *filename, unsigned const char *passphrase); + +int _libssh2_ecdsa_new_openssh_private(libssh2_ecdsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filename, + unsigned const char *passphrase); +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 +_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 +_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 +_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 _libssh2_ecdsa_new_private_frommemory(libssh2_ecdsa_ctx ** ec_ctx, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase); + +libssh2_curve_type +_libssh2_ecdsa_key_get_curve_type(_libssh2_ec_key *key); + +int +_libssh2_ecdsa_curve_type_from_name(const char *name, libssh2_curve_type *out_type); + +#endif /* LIBSSH2_ECDSA */ + int _libssh2_cipher_init(_libssh2_cipher_ctx * h, _libssh2_cipher_type(algo), unsigned char *iv, diff --git a/src/hostkey.c b/src/hostkey.c index 839ba16..3aad833 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -483,7 +483,296 @@ static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_dss = { }; #endif /* LIBSSH2_DSA */ +#if LIBSSH2_ECDSA + +/* *********** + * ecdsa-sha2-nistp256/384/521 * + *********** */ + +static int +hostkey_method_ssh_ecdsa_dtor(LIBSSH2_SESSION * session, + void **abstract); + +/* + * hostkey_method_ssh_ecdsa_init + * + * Initialize the server hostkey working area with e/n pair + */ +static int +hostkey_method_ssh_ecdsa_init(LIBSSH2_SESSION * session, + const unsigned char *hostkey_data, + size_t hostkey_data_len, + void **abstract) +{ + libssh2_ecdsa_ctx *ecdsactx = NULL; + const unsigned char *s, *k; + size_t len, key_len, n_len; + libssh2_curve_type type; + + if (abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + if ( hostkey_data_len < 23 ) + return -1; + + s = hostkey_data; + len = _libssh2_ntohu32(s); + s += 4; + + if (len != 19 ) + return -1; + + if (strncmp((char*) s, "ecdsa-sha2-nistp256", 19) == 0 ) { + type = LIBSSH2_EC_CURVE_NISTP256; + } else if(strncmp((char*) s, "ecdsa-sha2-nistp384", 19) == 0 ) { + type = LIBSSH2_EC_CURVE_NISTP384; + } else if(strncmp((char*) s, "ecdsa-sha2-nistp521", 19) == 0 ) { + type = LIBSSH2_EC_CURVE_NISTP521; + } else { + return -1; + } + s += 19; + + /* Domain length */ + n_len = _libssh2_ntohu32(s); + s += 4; + + if (n_len != 8) + return -1; + + if ( type == LIBSSH2_EC_CURVE_NISTP256 && strncmp((char*)s, "nistp256", 8) != 0) { + return -1; + } else if ( type == LIBSSH2_EC_CURVE_NISTP384 && strncmp((char*)s, "nistp384", 8) != 0) { + return -1; + } else if ( type == LIBSSH2_EC_CURVE_NISTP521 && strncmp((char*)s, "nistp521", 8) != 0) { + return -1; + } + + s += 8; + + /* public key */ + key_len = _libssh2_ntohu32(s); + s += 4; + + k = s; + + if (_libssh2_ecdsa_curve_name_with_octal_new(&ecdsactx, k, key_len, type) ) + return -1; + + if ( abstract != NULL ) + *abstract = ecdsactx; + + return 0; +} + +/* + * hostkey_method_ssh_ecdsa_initPEM + * + * Load a Private Key from a PEM file + */ +static int +hostkey_method_ssh_ecdsa_initPEM(LIBSSH2_SESSION * session, + const char *privkeyfile, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = NULL; + int ret; + + if (abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ecdsa_new_private(&ec_ctx, session, privkeyfile, passphrase); + + if ( abstract != NULL ) + *abstract = ec_ctx; + + return ret; +} + +/* + * hostkey_method_ssh_ecdsa_initPEMFromMemory + * + * Load a Private Key from memory + */ +static int +hostkey_method_ssh_ecdsa_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = NULL; + int ret; + + if (abstract != NULL && *abstract) { + hostkey_method_ssh_ecdsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_ecdsa_new_private_frommemory(&ec_ctx, session, + privkeyfiledata, + privkeyfiledata_len, passphrase); + if (ret) { + return -1; + } + + if (abstract != NULL) + *abstract = ec_ctx; + + return 0; +} + +/* + * hostkey_method_ecdsa_sig_verify + * + * Verify signature created by remote + */ +static int +hostkey_method_ssh_ecdsa_sig_verify(LIBSSH2_SESSION * session, + const unsigned char *sig, + size_t sig_len, + const unsigned char *m, + size_t m_len, void **abstract) +{ + const unsigned char *r, *s, *p; + size_t r_len, s_len; + libssh2_ecdsa_ctx *ctx = (libssh2_ecdsa_ctx *) (*abstract); + + (void) session; + + if ( sig_len < 35 ) + return -1; + + /* Skip past keyname_len(4) + keyname(19){"ecdsa-sha2-nistp256"} + signature_len(4) */ + p = sig; + p += 27; + + r_len = _libssh2_ntohu32(p); + p += 4; + r = p; + p += r_len; + + s_len = _libssh2_ntohu32(p); + p += 4; + s = p; + + return _libssh2_ecdsa_verify(ctx, r, r_len, s, s_len, m, m_len); +} + + +#define LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(digest_type) \ +{ \ + unsigned char hash[SHA##digest_type##_DIGEST_LENGTH]; \ + libssh2_sha##digest_type##_ctx ctx; \ + int i; \ + libssh2_sha##digest_type##_init(&ctx); \ + for(i = 0; i < veccount; i++) { \ + libssh2_sha##digest_type##_update(ctx, datavec[i].iov_base, datavec[i].iov_len); \ + } \ + libssh2_sha##digest_type##_final(ctx, hash); \ + ret = _libssh2_ecdsa_sign(session, ec_ctx, hash, SHA##digest_type##_DIGEST_LENGTH, \ + signature, signature_len); \ +} + + +/* + * hostkey_method_ecdsa_signv + * + * Construct a signature from an array of vectors + */ +static int +hostkey_method_ssh_ecdsa_signv(LIBSSH2_SESSION * session, + unsigned char **signature, + size_t *signature_len, + int veccount, + const struct iovec datavec[], + void **abstract) +{ + libssh2_ecdsa_ctx *ec_ctx = (libssh2_ecdsa_ctx *) (*abstract); + libssh2_curve_type type = _libssh2_ecdsa_key_get_curve_type(ec_ctx); + int ret = 0; + + if ( type == LIBSSH2_EC_CURVE_NISTP256 ) { + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(256); + }else if ( type == LIBSSH2_EC_CURVE_NISTP384 ) { + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(384); + }else if ( type == LIBSSH2_EC_CURVE_NISTP521 ){ + LIBSSH2_HOSTKEY_METHOD_EC_SIGNV_HASH(512); + }else{ + return -1; + } + + return ret; +} + +/* + * hostkey_method_ssh_ecdsa_dtor + * + * Shutdown the hostkey by freeing EC_KEY context + */ +static int +hostkey_method_ssh_ecdsa_dtor(LIBSSH2_SESSION * session, void **abstract) +{ + libssh2_ecdsa_ctx *keyctx = (libssh2_ecdsa_ctx *) (*abstract); + (void) session; + + if (keyctx != NULL) + _libssh2_ecdsa_free(keyctx); + + *abstract = NULL; + + return 0; +} + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp256 = { + "ecdsa-sha2-nistp256", + SHA256_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp384 = { + "ecdsa-sha2-nistp384", + SHA384_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; + +static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ecdsa_ssh_nistp521 = { + "ecdsa-sha2-nistp521", + SHA512_DIGEST_LENGTH, + hostkey_method_ssh_ecdsa_init, + hostkey_method_ssh_ecdsa_initPEM, + hostkey_method_ssh_ecdsa_initPEMFromMemory, + hostkey_method_ssh_ecdsa_sig_verify, + hostkey_method_ssh_ecdsa_signv, + NULL, /* encrypt */ + hostkey_method_ssh_ecdsa_dtor, +}; +#endif /* LIBSSH2_ECDSA */ + + static const LIBSSH2_HOSTKEY_METHOD *hostkey_methods[] = { +#if LIBSSH2_ECDSA + &hostkey_method_ecdsa_ssh_nistp256, + &hostkey_method_ecdsa_ssh_nistp384, + &hostkey_method_ecdsa_ssh_nistp521, +#endif #if LIBSSH2_RSA &hostkey_method_ssh_rsa, #endif /* LIBSSH2_RSA */ @@ -541,6 +830,15 @@ static int hostkey_type(const unsigned char *hostkey, size_t len) const unsigned char dss[] = { 0, 0, 0, 0x07, 's', 's', 'h', '-', 'd', 's', 's' }; + const unsigned char ecdsa_256[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', 'n', 'i', 's', 't', 'p', '2', '5', '6' + }; + const unsigned char ecdsa_384[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', 'n', 'i', 's', 't', 'p', '3', '8', '4' + }; + const unsigned char ecdsa_521[] = { + 0, 0, 0, 0x13, 'e', 'c', 'd', 's', 'a', '-', 's', 'h', 'a', '2', '-', 'n', 'i', 's', 't', 'p', '5', '2', '1' + }; if (len < 11) return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; @@ -551,6 +849,21 @@ static int hostkey_type(const unsigned char *hostkey, size_t len) if (!memcmp(dss, hostkey, 11)) return LIBSSH2_HOSTKEY_TYPE_DSS; + if ( len < 15 ) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if ( len < 23 ) + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; + + if (!memcmp(ecdsa_256, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA; + + if (!memcmp(ecdsa_384, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA; + + if (!memcmp(ecdsa_521, hostkey, 23)) + return LIBSSH2_HOSTKEY_TYPE_ECDSA; + return LIBSSH2_HOSTKEY_TYPE_UNKNOWN; } @@ -575,4 +888,3 @@ libssh2_session_hostkey(LIBSSH2_SESSION *session, size_t *len, int *type) *len = 0; return NULL; } - diff --git a/src/kex.c b/src/kex.c index 330b02d..d806c73 100644 --- a/src/kex.c +++ b/src/kex.c @@ -68,34 +68,47 @@ libssh2_sha1_final(hash, (value) + len); \ len += SHA_DIGEST_LENGTH; \ } \ - } + } \ -/* Helper macro called from kex_method_diffie_hellman_group1_sha256_key_exchange */ -#define LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(value, reqlen, version) \ - { \ - libssh2_sha256_ctx hash; \ - unsigned long len = 0; \ - if (!(value)) { \ - value = LIBSSH2_ALLOC(session, reqlen + SHA256_DIGEST_LENGTH); \ - } \ - if (value) \ - while (len < (unsigned long)reqlen) { \ - libssh2_sha256_init(&hash); \ - libssh2_sha256_update(hash, exchange_state->k_value, \ - exchange_state->k_value_len); \ - libssh2_sha256_update(hash, exchange_state->h_sig_comp, \ - SHA256_DIGEST_LENGTH); \ - if (len > 0) { \ - libssh2_sha256_update(hash, value, len); \ - } else { \ - libssh2_sha256_update(hash, (version), 1); \ - libssh2_sha256_update(hash, session->session_id, \ - session->session_id_len); \ - } \ - libssh2_sha256_final(hash, (value) + len); \ - len += SHA256_DIGEST_LENGTH; \ - } \ +#define LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(value, reqlen, version) \ + { \ + if (type == LIBSSH2_EC_CURVE_NISTP256) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, value, reqlen, version); \ + } \ + else if (type == LIBSSH2_EC_CURVE_NISTP384 ) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(384, value, reqlen, version); \ + } \ + else if (type == LIBSSH2_EC_CURVE_NISTP521 ) { \ + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(512, value, reqlen, version); \ + } \ +} \ + + +#define LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(digest_type, value, reqlen, version) \ + { \ + libssh2_sha##digest_type##_ctx hash; \ + unsigned long len = 0; \ + if (!(value)) { \ + value = LIBSSH2_ALLOC(session, reqlen + SHA##digest_type##_DIGEST_LENGTH); \ + } \ + if (value) \ + while (len < (unsigned long)reqlen) { \ + libssh2_sha##digest_type##_init(&hash); \ + libssh2_sha##digest_type##_update(hash, exchange_state->k_value, \ + exchange_state->k_value_len); \ + libssh2_sha##digest_type##_update(hash, exchange_state->h_sig_comp, \ + SHA##digest_type##_DIGEST_LENGTH); \ + if (len > 0) { \ + libssh2_sha##digest_type##_update(hash, value, len); \ + } else { \ + libssh2_sha##digest_type##_update(hash, (version), 1); \ + libssh2_sha##digest_type##_update(hash, session->session_id, \ + session->session_id_len); \ + } \ + libssh2_sha##digest_type##_final(hash, (value) + len); \ + len += SHA##digest_type##_DIGEST_LENGTH; \ + } \ } @@ -305,10 +318,10 @@ static int diffie_hellman_sha1(LIBSSH2_SESSION *session, "Server's SHA1 Fingerprint: %s", fingerprint); } #endif /* LIBSSH2DEBUG */ - + { libssh2_sha256_ctx fingerprint_ctx; - + if (libssh2_sha256_init(&fingerprint_ctx)) { libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); @@ -959,10 +972,10 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, "Server's SHA1 Fingerprint: %s", fingerprint); } #endif /* LIBSSH2DEBUG */ - + { libssh2_sha256_ctx fingerprint_ctx; - + if (libssh2_sha256_init(&fingerprint_ctx)) { libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, session->server_hostkey_len); @@ -1199,18 +1212,18 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, unsigned char *iv = NULL, *secret = NULL; int free_iv = 0, free_secret = 0; - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(iv, - session->local.crypt-> - iv_len, - (const unsigned char *)"A"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, iv, + session->local.crypt-> + iv_len, + (const unsigned char *)"A"); if (!iv) { ret = -1; goto clean_exit; } - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(secret, - session->local.crypt-> - secret_len, - (const unsigned char *)"C"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, secret, + session->local.crypt-> + secret_len, + (const unsigned char *)"C"); if (!secret) { LIBSSH2_FREE(session, iv); ret = LIBSSH2_ERROR_KEX_FAILURE; @@ -1248,18 +1261,18 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, unsigned char *iv = NULL, *secret = NULL; int free_iv = 0, free_secret = 0; - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(iv, - session->remote.crypt-> - iv_len, - (const unsigned char *)"B"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, iv, + session->remote.crypt-> + iv_len, + (const unsigned char *)"B"); if (!iv) { ret = LIBSSH2_ERROR_KEX_FAILURE; goto clean_exit; } - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(secret, - session->remote.crypt-> - secret_len, - (const unsigned char *)"D"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, secret, + session->remote.crypt-> + secret_len, + (const unsigned char *)"D"); if (!secret) { LIBSSH2_FREE(session, iv); ret = LIBSSH2_ERROR_KEX_FAILURE; @@ -1295,10 +1308,10 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, unsigned char *key = NULL; int free_key = 0; - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(key, - session->local.mac-> - key_len, - (const unsigned char *)"E"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, key, + session->local.mac-> + key_len, + (const unsigned char *)"E"); if (!key) { ret = LIBSSH2_ERROR_KEX_FAILURE; goto clean_exit; @@ -1322,10 +1335,10 @@ static int diffie_hellman_sha256(LIBSSH2_SESSION *session, unsigned char *key = NULL; int free_key = 0; - LIBSSH2_KEX_METHOD_DIFFIE_HELLMAN_SHA256_HASH(key, - session->remote.mac-> - key_len, - (const unsigned char *)"F"); + LIBSSH2_KEX_METHOD_SHA_VALUE_HASH(256, key, + session->remote.mac-> + key_len, + (const unsigned char *)"F"); if (!key) { ret = LIBSSH2_ERROR_KEX_FAILURE; goto clean_exit; @@ -1745,6 +1758,725 @@ kex_method_diffie_hellman_group_exchange_sha256_key_exchange } +#if LIBSSH2_ECDSA + +/* kex_session_ecdh_curve_type + * returns the EC curve type by name used in key exchange + */ + +static int +kex_session_ecdh_curve_type(const char *name, libssh2_curve_type *out_type) +{ + int ret = 0; + libssh2_curve_type type; + + if ( name == NULL ) + return -1; + + if ( strcmp(name, "ecdh-sha2-nistp256") == 0) + type = LIBSSH2_EC_CURVE_NISTP256; + else if ( strcmp(name, "ecdh-sha2-nistp384") == 0) + type = LIBSSH2_EC_CURVE_NISTP384; + else if ( strcmp(name, "ecdh-sha2-nistp521") == 0) + type = LIBSSH2_EC_CURVE_NISTP521; + else { + ret = -1; + } + + if (ret == 0 && out_type) { + *out_type = type; + } + + return ret; +} + + +/* LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY + * + * Macro that create and verifies EC SHA hash with a given digest bytes + * + * Payload format: + * + * string V_C, client's identification string (CR and LF excluded) + * string V_S, server's identification string (CR and LF excluded) + * string I_C, payload of the client's SSH_MSG_KEXINIT + * string I_S, payload of the server's SSH_MSG_KEXINIT + * string K_S, server's public host key + * string Q_C, client's ephemeral public key octet string + * string Q_S, server's ephemeral public key octet string + * mpint K, shared secret + * + */ + +#define LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(digest_type) \ + { \ + \ + libssh2_sha##digest_type##_ctx ctx; \ + exchange_state->exchange_hash = (void*)&ctx; \ + \ + libssh2_sha##digest_type##_init(&ctx); \ + if (session->local.banner) { \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + strlen((char *) session->local.banner) - 2); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + (char *) session->local.banner, \ + strlen((char *) session->local.banner) - 2); \ + } else { \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + LIBSSH2_SSH_DEFAULT_BANNER, \ + sizeof(LIBSSH2_SSH_DEFAULT_BANNER) - 1); \ + } \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + strlen((char *) session->remote.banner)); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->remote.banner, \ + strlen((char *) session->remote.banner)); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->local.kexinit_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->local.kexinit, \ + session->local.kexinit_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->remote.kexinit_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->remote.kexinit, \ + session->remote.kexinit_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + session->server_hostkey_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + session->server_hostkey, \ + session->server_hostkey_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + public_key_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + public_key, \ + public_key_len); \ + \ + _libssh2_htonu32(exchange_state->h_sig_comp, \ + server_public_key_len); \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->h_sig_comp, 4); \ + libssh2_sha##digest_type##_update(ctx, \ + server_public_key, \ + server_public_key_len); \ + \ + libssh2_sha##digest_type##_update(ctx, \ + exchange_state->k_value, \ + exchange_state->k_value_len); \ + \ + libssh2_sha##digest_type##_final(ctx, exchange_state->h_sig_comp); \ + \ + if (session->hostkey-> \ + sig_verify(session, exchange_state->h_sig, \ + exchange_state->h_sig_len, exchange_state->h_sig_comp, \ + SHA##digest_type##_DIGEST_LENGTH, &session->server_hostkey_abstract)) { \ + rc = -1; \ + } \ + } \ + + +/* ecdh_sha2_nistp + * Elliptic Curve Diffie Hellman Key Exchange + */ + +static int ecdh_sha2_nistp(LIBSSH2_SESSION *session, libssh2_curve_type type, + unsigned char *data, size_t data_len, unsigned char *public_key, + size_t public_key_len, _libssh2_ec_key *private_key, + kmdhgGPshakex_state_t *exchange_state) +{ + int ret = 0; + int rc; + + if (exchange_state->state == libssh2_NB_state_idle) { + + /* Setup initial values */ + exchange_state->k = _libssh2_bn_init(); + + exchange_state->state = libssh2_NB_state_created; + } + + if ( exchange_state->state == libssh2_NB_state_created ) + { + /* parse INIT reply data */ + + /* host key K_S */ + unsigned char *s = data + 1; /* Advance past packet type */ + unsigned char *server_public_key; + size_t server_public_key_len; + size_t host_sig_len; + + session->server_hostkey_len = _libssh2_ntohu32((const unsigned char*)s); + s += 4; + + session->server_hostkey = LIBSSH2_ALLOC(session, session->server_hostkey_len); + if (!session->server_hostkey) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for a copy " + "of the host key"); + goto clean_exit; + } + + memcpy(session->server_hostkey, s, session->server_hostkey_len); + s += session->server_hostkey_len; + +#if LIBSSH2_MD5 + { + libssh2_md5_ctx fingerprint_ctx; + + if (libssh2_md5_init(&fingerprint_ctx)) { + libssh2_md5_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_md5_final(fingerprint_ctx, session->server_hostkey_md5); + session->server_hostkey_md5_valid = TRUE; + } + else { + session->server_hostkey_md5_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[50], *fprint = fingerprint; + int i; + for(i = 0; i < 16; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_md5[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's MD5 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ +#endif /* ! LIBSSH2_MD5 */ + + { + libssh2_sha1_ctx fingerprint_ctx; + + if (libssh2_sha1_init(&fingerprint_ctx)) { + libssh2_sha1_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha1_final(fingerprint_ctx, session->server_hostkey_sha1); + session->server_hostkey_sha1_valid = TRUE; + } + else { + session->server_hostkey_sha1_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char fingerprint[64], *fprint = fingerprint; + int i; + + for(i = 0; i < 20; i++, fprint += 3) { + snprintf(fprint, 4, "%02x:", session->server_hostkey_sha1[i]); + } + *(--fprint) = '\0'; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA1 Fingerprint: %s", fingerprint); + } +#endif /* LIBSSH2DEBUG */ + + /* SHA256 */ + { + libssh2_sha256_ctx fingerprint_ctx; + + if (libssh2_sha256_init(&fingerprint_ctx)) { + libssh2_sha256_update(fingerprint_ctx, session->server_hostkey, + session->server_hostkey_len); + libssh2_sha256_final(fingerprint_ctx, session->server_hostkey_sha256); + session->server_hostkey_sha256_valid = TRUE; + } + else { + session->server_hostkey_sha256_valid = FALSE; + } + } +#ifdef LIBSSH2DEBUG + { + char *base64Fingerprint = NULL; + _libssh2_base64_encode(session, (const char*)session->server_hostkey_sha256, SHA256_DIGEST_LENGTH, &base64Fingerprint); + if ( base64Fingerprint != NULL ) { + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server's SHA256 Fingerprint: %s", base64Fingerprint); + LIBSSH2_FREE(session, base64Fingerprint); + } + } +#endif /* LIBSSH2DEBUG */ + + if (session->hostkey->init(session, session->server_hostkey, + session->server_hostkey_len, + &session->server_hostkey_abstract)) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_INIT, + "Unable to initialize hostkey importer"); + goto clean_exit; + } + + /* server public key Q_S */ + server_public_key_len = _libssh2_ntohu32((const unsigned char*)s); + s += 4; + + server_public_key = s; + s += server_public_key_len; + + /* server signature */ + host_sig_len = _libssh2_ntohu32((const unsigned char*)s); + s += 4; + + exchange_state->h_sig = s; + exchange_state->h_sig_len = host_sig_len; + s += host_sig_len; + + /* Compute the shared secret K */ + rc = _libssh2_ecdh_gen_k(&exchange_state->k, private_key, server_public_key, server_public_key_len); + if ( rc != 0 ) { + ret = _libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, + "Unable to create ECDH shared secret"); + goto clean_exit; + } + + exchange_state->k_value_len = _libssh2_bn_bytes(exchange_state->k) + 5; + if (_libssh2_bn_bits(exchange_state->k) % 8) { + /* don't need leading 00 */ + exchange_state->k_value_len--; + } + exchange_state->k_value = + LIBSSH2_ALLOC(session, exchange_state->k_value_len); + if (!exchange_state->k_value) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for K"); + goto clean_exit; + } + _libssh2_htonu32(exchange_state->k_value, + exchange_state->k_value_len - 4); + if (_libssh2_bn_bits(exchange_state->k) % 8) { + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 4); + } else { + exchange_state->k_value[4] = 0; + _libssh2_bn_to_bin(exchange_state->k, exchange_state->k_value + 5); + } + + /* verify hash */ + + switch ( type ) { + case LIBSSH2_EC_CURVE_NISTP256: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(256); + break; + + case LIBSSH2_EC_CURVE_NISTP384: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(384); + break; + case LIBSSH2_EC_CURVE_NISTP521: + LIBSSH2_KEX_METHOD_EC_SHA_HASH_CREATE_VERIFY(512); + break; + } + + if ( rc != 0 ) { + ret = _libssh2_error(session, LIBSSH2_ERROR_HOSTKEY_SIGN, + "Unable to verify hostkey signature"); + goto clean_exit; + } + + exchange_state->c = SSH_MSG_NEWKEYS; + exchange_state->state = libssh2_NB_state_sent; + } + + if (exchange_state->state == libssh2_NB_state_sent) { + rc = _libssh2_transport_send(session, &exchange_state->c, 1, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, "Unable to send NEWKEYS message"); + goto clean_exit; + } + + exchange_state->state = libssh2_NB_state_sent2; + } + + if (exchange_state->state == libssh2_NB_state_sent2) { + rc = _libssh2_packet_require(session, SSH_MSG_NEWKEYS, + &exchange_state->tmp, + &exchange_state->tmp_len, 0, NULL, 0, + &exchange_state->req_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, "Timed out waiting for NEWKEYS"); + goto clean_exit; + } + + /* The first key exchange has been performed, + switch to active crypt/comp/mac mode */ + session->state |= LIBSSH2_STATE_NEWKEYS; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "Received NEWKEYS message"); + + /* This will actually end up being just packet_type(1) + for this packet type anyway */ + LIBSSH2_FREE(session, exchange_state->tmp); + + if (!session->session_id) { + + size_t digest_length = 0; + + if ( type == LIBSSH2_EC_CURVE_NISTP256 ) + digest_length = SHA256_DIGEST_LENGTH; + else if ( type == LIBSSH2_EC_CURVE_NISTP384 ) + digest_length = SHA384_DIGEST_LENGTH; + else if ( type == LIBSSH2_EC_CURVE_NISTP521 ) + digest_length = SHA512_DIGEST_LENGTH; + else{ + ret = _libssh2_error(session, LIBSSH2_ERROR_KEX_FAILURE, + "Unknown SHA digest for EC curve"); + goto clean_exit; + + } + session->session_id = LIBSSH2_ALLOC(session, digest_length); + if (!session->session_id) { + ret = _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate buffer for SHA digest"); + goto clean_exit; + } + memcpy(session->session_id, exchange_state->h_sig_comp, + digest_length); + session->session_id_len = digest_length; + _libssh2_debug(session, LIBSSH2_TRACE_KEX, "session_id calculated"); + } + + /* Cleanup any existing cipher */ + if (session->local.crypt->dtor) { + session->local.crypt->dtor(session, + &session->local.crypt_abstract); + } + + /* Calculate IV/Secret/Key for each direction */ + if (session->local.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(iv, + session->local.crypt-> + iv_len, "A"); + if (!iv) { + ret = -1; + goto clean_exit; + } + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(secret, + session->local.crypt-> + secret_len, "C"); + + if (!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if (session->local.crypt-> + init(session, session->local.crypt, iv, &free_iv, secret, + &free_secret, 1, &session->local.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if (free_iv) { + memset(iv, 0, session->local.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->local.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server IV and Key calculated"); + + if (session->remote.crypt->dtor) { + /* Cleanup any existing cipher */ + session->remote.crypt->dtor(session, + &session->remote.crypt_abstract); + } + + if (session->remote.crypt->init) { + unsigned char *iv = NULL, *secret = NULL; + int free_iv = 0, free_secret = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(iv, + session->remote.crypt-> + iv_len, "B"); + + if (!iv) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(secret, + session->remote.crypt-> + secret_len, "D"); + + if (!secret) { + LIBSSH2_FREE(session, iv); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + if (session->remote.crypt-> + init(session, session->remote.crypt, iv, &free_iv, secret, + &free_secret, 0, &session->remote.crypt_abstract)) { + LIBSSH2_FREE(session, iv); + LIBSSH2_FREE(session, secret); + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + + if (free_iv) { + memset(iv, 0, session->remote.crypt->iv_len); + LIBSSH2_FREE(session, iv); + } + + if (free_secret) { + memset(secret, 0, session->remote.crypt->secret_len); + LIBSSH2_FREE(session, secret); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client IV and Key calculated"); + + if (session->local.mac->dtor) { + session->local.mac->dtor(session, &session->local.mac_abstract); + } + + if (session->local.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(key, + session->local.mac-> + key_len, "E"); + + if (!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->local.mac->init(session, key, &free_key, + &session->local.mac_abstract); + + if (free_key) { + memset(key, 0, session->local.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server HMAC Key calculated"); + + if (session->remote.mac->dtor) { + session->remote.mac->dtor(session, &session->remote.mac_abstract); + } + + if (session->remote.mac->init) { + unsigned char *key = NULL; + int free_key = 0; + + LIBSSH2_KEX_METHOD_EC_SHA_VALUE_HASH(key, + session->remote.mac-> + key_len, "F"); + + if (!key) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + session->remote.mac->init(session, key, &free_key, + &session->remote.mac_abstract); + + if (free_key) { + memset(key, 0, session->remote.mac->key_len); + LIBSSH2_FREE(session, key); + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client HMAC Key calculated"); + + /* Initialize compression for each direction */ + + /* Cleanup any existing compression */ + if (session->local.comp && session->local.comp->dtor) { + session->local.comp->dtor(session, 1, + &session->local.comp_abstract); + } + + if (session->local.comp && session->local.comp->init) { + if (session->local.comp->init(session, 1, + &session->local.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Client to Server compression initialized"); + + if (session->remote.comp && session->remote.comp->dtor) { + session->remote.comp->dtor(session, 0, + &session->remote.comp_abstract); + } + + if (session->remote.comp && session->remote.comp->init) { + if (session->remote.comp->init(session, 0, + &session->remote.comp_abstract)) { + ret = LIBSSH2_ERROR_KEX_FAILURE; + goto clean_exit; + } + } + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Server to Client compression initialized"); + + } + +clean_exit: + _libssh2_bn_free(exchange_state->k); + exchange_state->k = NULL; + + if (exchange_state->k_value) { + LIBSSH2_FREE(session, exchange_state->k_value); + exchange_state->k_value = NULL; + } + + exchange_state->state = libssh2_NB_state_idle; + + return ret; +} + +/* kex_method_ecdh_key_exchange + * + * Elliptic Curve Diffie Hellman Key Exchange + * supports SHA256/384/512 hashes based on negotated ecdh method + * + */ + +static int +kex_method_ecdh_key_exchange +(LIBSSH2_SESSION * session, key_exchange_state_low_t * key_state) +{ + int ret = 0; + int rc = 0; + unsigned char *s; + libssh2_curve_type type; + + if (key_state->state == libssh2_NB_state_idle) { + + key_state->public_key_oct = NULL; + key_state->state = libssh2_NB_state_created; + } + + if ( key_state->state == libssh2_NB_state_created ) + { + rc = kex_session_ecdh_curve_type(session->kex->name, &type); + + if ( rc != 0 ) { + ret = _libssh2_error(session, -1, + "Unknown KEX nistp curve type"); + goto ecdh_clean_exit; + } + + rc = _libssh2_ecdsa_create_key(&key_state->private_key, &key_state->public_key_oct, + &key_state->public_key_oct_len, type); + + if ( rc != 0 ) + { + ret = _libssh2_error(session, rc, + "Unable to create private key"); + goto ecdh_clean_exit; + } + + key_state->request[0] = SSH2_MSG_KEX_ECDH_INIT; + s = key_state->request + 1; + _libssh2_store_str(&s, (const char*)key_state->public_key_oct, key_state->public_key_oct_len); + key_state->request_len = key_state->public_key_oct_len + 5; + + _libssh2_debug(session, LIBSSH2_TRACE_KEX, + "Initiating ECDH SHA2 NISTP256"); + + key_state->state = libssh2_NB_state_sent; + } + + if ( key_state->state == libssh2_NB_state_sent ) { + rc = _libssh2_transport_send(session, key_state->request, + key_state->request_len, NULL, 0); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, + "Unable to send ECDH_INIT"); + goto ecdh_clean_exit; + } + + key_state->state = libssh2_NB_state_sent1; + } + + if ( key_state->state == libssh2_NB_state_sent1 ) { + rc = _libssh2_packet_require(session, SSH2_MSG_KEX_ECDH_REPLY, + &key_state->data, &key_state->data_len, + 0, NULL, 0, &key_state->req_state); + if (rc == LIBSSH2_ERROR_EAGAIN) { + return rc; + } else if (rc) { + ret = _libssh2_error(session, rc, + "Timeout waiting for ECDH_REPLY reply"); + goto ecdh_clean_exit; + } + + key_state->state = libssh2_NB_state_sent2; + } + + if ( key_state->state == libssh2_NB_state_sent2 ) { + + (void)kex_session_ecdh_curve_type(session->kex->name, &type); + + ret = ecdh_sha2_nistp(session, type, key_state->data, key_state->data_len, (unsigned char*)key_state->public_key_oct, + key_state->public_key_oct_len, key_state->private_key, &key_state->exchange_state); + + if (ret == LIBSSH2_ERROR_EAGAIN) { + return ret; + } + + LIBSSH2_FREE(session, key_state->data); + } + +ecdh_clean_exit: + + if (key_state->public_key_oct) { + free(key_state->public_key_oct); + key_state->public_key_oct = NULL; + } + + if (key_state->private_key) { + _libssh2_ecdsa_free(key_state->private_key); + key_state->private_key = NULL; + } + + key_state->state = libssh2_NB_state_idle; + + return ret; +} + +#endif /*LIBSSH2_ECDSA*/ + + #define LIBSSH2_KEX_METHOD_FLAG_REQ_ENC_HOSTKEY 0x0001 #define LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY 0x0002 @@ -1774,7 +2506,35 @@ kex_method_diffie_helman_group_exchange_sha256 = { LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, }; +#if LIBSSH2_ECDSA +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp256 = { + "ecdh-sha2-nistp256", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp384 = { + "ecdh-sha2-nistp384", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; + +static const LIBSSH2_KEX_METHOD +kex_method_ecdh_sha2_nistp521 = { + "ecdh-sha2-nistp521", + kex_method_ecdh_key_exchange, + LIBSSH2_KEX_METHOD_FLAG_REQ_SIGN_HOSTKEY, +}; +#endif + static const LIBSSH2_KEX_METHOD *libssh2_kex_methods[] = { +#if LIBSSH2_ECDSA + &kex_method_ecdh_sha2_nistp256, + &kex_method_ecdh_sha2_nistp384, + &kex_method_ecdh_sha2_nistp521, +#endif &kex_method_diffie_helman_group_exchange_sha256, &kex_method_diffie_helman_group_exchange_sha1, &kex_method_diffie_helman_group14_sha1, diff --git a/src/libgcrypt.h b/src/libgcrypt.h index 113e5b2..4070e3a 100644 --- a/src/libgcrypt.h +++ b/src/libgcrypt.h @@ -54,10 +54,15 @@ #define LIBSSH2_RSA 1 #define LIBSSH2_DSA 1 +#define LIBSSH2_ECDSA 0 #define MD5_DIGEST_LENGTH 16 #define SHA_DIGEST_LENGTH 20 #define SHA256_DIGEST_LENGTH 32 +#define SHA384_DIGEST_LENGTH 48 +#define SHA512_DIGEST_LENGTH 64 + +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) #define _libssh2_random(buf, len) \ (gcry_randomize ((buf), (len), GCRY_STRONG_RANDOM), 1) @@ -87,6 +92,28 @@ #define libssh2_sha256(message, len, out) \ gcry_md_hash_buffer (GCRY_MD_SHA256, out, message, len) +#define libssh2_sha384_ctx gcry_md_hd_t + +#define libssh2_sha384_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open (ctx, GCRY_MD_SHA384, 0)) +#define libssh2_sha384_update(ctx, data, len) \ + gcry_md_write (ctx, (unsigned char *) data, len) +#define libssh2_sha384_final(ctx, out) \ + memcpy (out, gcry_md_read (ctx, 0), SHA384_DIGEST_LENGTH), gcry_md_close (ctx) +#define libssh2_sha384(message, len, out) \ + gcry_md_hash_buffer (GCRY_MD_SHA384, out, message, len) + +#define libssh2_sha512_ctx gcry_md_hd_t + +#define libssh2_sha512_init(ctx) \ + (GPG_ERR_NO_ERROR == gcry_md_open (ctx, GCRY_MD_SHA512, 0)) +#define libssh2_sha512_update(ctx, data, len) \ + gcry_md_write (ctx, (unsigned char *) data, len) +#define libssh2_sha512_final(ctx, out) \ + memcpy (out, gcry_md_read (ctx, 0), SHA512_DIGEST_LENGTH), gcry_md_close (ctx) +#define libssh2_sha512(message, len, out) \ + gcry_md_hash_buffer (GCRY_MD_SHA512, out, message, len) + #define libssh2_md5_ctx gcry_md_hd_t /* returns 0 in case of failure */ @@ -135,6 +162,11 @@ #define _libssh2_dsa_free(dsactx) gcry_sexp_release (dsactx) +#if LIBSSH2_ECDSA +#else +#define _libssh2_ec_key void +#endif + #define _libssh2_cipher_type(name) int name #define _libssh2_cipher_ctx gcry_cipher_hd_t diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index 2e3e634..5a32b3f 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -154,7 +154,7 @@ static inline int writev(int sock, struct iovec *iov, int nvecs) * padding length, payload, padding, and MAC.)." */ #define MAX_SSH_PACKET_LEN 35000 -#define MAX_SHA_DIGEST_LEN SHA256_DIGEST_LENGTH +#define MAX_SHA_DIGEST_LEN SHA512_DIGEST_LENGTH #define LIBSSH2_ALLOC(session, count) \ session->alloc((count), &(session)->abstract) @@ -271,10 +271,13 @@ typedef struct key_exchange_state_low_t kmdhgGPshakex_state_t exchange_state; _libssh2_bn *p; /* SSH2 defined value (p_value) */ _libssh2_bn *g; /* SSH2 defined value (2) */ - unsigned char request[13]; + unsigned char request[256]; /* Must fit EC_MAX_POINT_LEN + data */ unsigned char *data; size_t request_len; size_t data_len; + _libssh2_ec_key *private_key; /* SSH2 ecdh private key */ + unsigned char *public_key_oct; /* SSH2 ecdh public key octal value */ + size_t public_key_oct_len; /* SSH2 ecdh public key octal value length */ } key_exchange_state_low_t; typedef struct key_exchange_state_t @@ -987,6 +990,10 @@ _libssh2_debug(LIBSSH2_SESSION * session, int context, const char *format, ...) #define SSH_MSG_KEX_DH_GEX_INIT 32 #define SSH_MSG_KEX_DH_GEX_REPLY 33 +/* ecdh */ +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 + /* User Authentication */ #define SSH_MSG_USERAUTH_REQUEST 50 #define SSH_MSG_USERAUTH_FAILURE 51 diff --git a/src/mbedtls.h b/src/mbedtls.h index 9ed787e..9fe95cd 100644 --- a/src/mbedtls.h +++ b/src/mbedtls.h @@ -27,12 +27,21 @@ #define LIBSSH2_RSA 1 #define LIBSSH2_DSA 0 +#define LIBSSH2_ECDSA 0 #define MD5_DIGEST_LENGTH 16 #define SHA_DIGEST_LENGTH 20 #define SHA256_DIGEST_LENGTH 32 +#define SHA384_DIGEST_LENGTH 48 #define SHA512_DIGEST_LENGTH 64 +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) + +#if LIBSSH2_ECDSA +#else +#define _libssh2_ec_key void +#endif + /*******************************************************************/ /* * mbedTLS backend: Global context handles @@ -80,6 +89,8 @@ mbedtls_ctr_drbg_context _libssh2_mbedtls_ctr_drbg; _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_RIPEMD160, key, keylen) #define libssh2_hmac_sha256_init(pctx, key, keylen) \ _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA256, key, keylen) +#define libssh2_hmac_sha384_init(pctx, key, keylen) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA384, key, keylen) #define libssh2_hmac_sha512_init(pctx, key, keylen) \ _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA512, key, keylen) @@ -117,6 +128,23 @@ mbedtls_ctr_drbg_context _libssh2_mbedtls_ctr_drbg; _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA256, hash) +/*******************************************************************/ +/* + * mbedTLS backend: SHA384 functions + */ + +#define libssh2_sha384_ctx mbedtls_md_context_t + +#define libssh2_sha384_init(pctx) \ + _libssh2_mbedtls_hash_init(pctx, MBEDTLS_MD_SHA384, NULL, 0) +#define libssh2_sha384_update(ctx, data, datalen) \ + mbedtls_md_update(&ctx, (unsigned char *) data, datalen) +#define libssh2_sha384_final(ctx, hash) \ + _libssh2_mbedtls_hash_final(&ctx, hash) +#define libssh2_sha384(data, datalen, hash) \ + _libssh2_mbedtls_hash(data, datalen, MBEDTLS_MD_SHA384, hash) + + /*******************************************************************/ /* * mbedTLS backend: SHA512 functions diff --git a/src/openssl.c b/src/openssl.c index 2ad0b26..c04955f 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -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, diff --git a/src/openssl.h b/src/openssl.h index 0de14b0..3399c2e 100644 --- a/src/openssl.h +++ b/src/openssl.h @@ -70,6 +70,12 @@ # define LIBSSH2_DSA 1 #endif +#ifdef OPENSSL_NO_ECDSA +# define LIBSSH2_ECDSA 0 +#else +# define LIBSSH2_ECDSA 1 +#endif + #ifdef OPENSSL_NO_MD5 # define LIBSSH2_MD5 0 #else @@ -117,6 +123,8 @@ # define LIBSSH2_3DES 1 #endif +#define EC_MAX_POINT_LEN ((528 * 2 / 8) + 1) + #define _libssh2_random(buf, len) RAND_bytes ((buf), (len)) #define libssh2_prepare_iovec(vec, len) /* Empty. */ @@ -167,6 +175,52 @@ int _libssh2_sha256(const unsigned char *message, unsigned long len, unsigned char *out); #define libssh2_sha256(x,y,z) _libssh2_sha256(x,y,z) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha384_ctx EVP_MD_CTX * +#else +#define libssh2_sha384_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha384_init(libssh2_sha384_ctx *ctx); +#define libssh2_sha384_init(x) _libssh2_sha384_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha384_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha384_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha384_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha384_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha384(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha384(x,y,z) _libssh2_sha384(x,y,z) + +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha512_ctx EVP_MD_CTX * +#else +#define libssh2_sha512_ctx EVP_MD_CTX +#endif + +/* returns 0 in case of failure */ +int _libssh2_sha512_init(libssh2_sha512_ctx *ctx); +#define libssh2_sha512_init(x) _libssh2_sha512_init(x) +#ifdef HAVE_OPAQUE_STRUCTS +#define libssh2_sha512_update(ctx, data, len) EVP_DigestUpdate(ctx, data, len) +#define libssh2_sha512_final(ctx, out) do { \ + EVP_DigestFinal(ctx, out, NULL); \ + EVP_MD_CTX_free(ctx); \ + } while(0) +#else +#define libssh2_sha512_update(ctx, data, len) EVP_DigestUpdate(&(ctx), data, len) +#define libssh2_sha512_final(ctx, out) EVP_DigestFinal(&(ctx), out, NULL) +#endif +int _libssh2_sha512(const unsigned char *message, unsigned long len, + unsigned char *out); +#define libssh2_sha512(x,y,z) _libssh2_sha512(x,y,z) + #ifdef HAVE_OPAQUE_STRUCTS #define libssh2_md5_ctx EVP_MD_CTX * #else @@ -246,9 +300,23 @@ int _libssh2_md5_init(libssh2_md5_ctx *ctx); #define libssh2_dsa_ctx DSA - #define _libssh2_dsa_free(dsactx) DSA_free(dsactx) +#if LIBSSH2_ECDSA +#define libssh2_ecdsa_ctx EC_KEY +#define _libssh2_ecdsa_free(ecdsactx) EC_KEY_free(ecdsactx) +#define _libssh2_ec_key EC_KEY + +typedef enum { + LIBSSH2_EC_CURVE_NISTP256 = NID_X9_62_prime256v1, + LIBSSH2_EC_CURVE_NISTP384 = NID_secp384r1, + LIBSSH2_EC_CURVE_NISTP521 = NID_secp521r1, +} +libssh2_curve_type; +#else +#define _libssh2_ec_key void +#endif + #define _libssh2_cipher_type(name) const EVP_CIPHER *(*name)(void) #ifdef HAVE_OPAQUE_STRUCTS #define _libssh2_cipher_ctx EVP_CIPHER_CTX * @@ -311,4 +379,3 @@ extern void _libssh2_dh_dtor(_libssh2_dh_ctx *dhctx); const EVP_CIPHER *_libssh2_EVP_aes_128_ctr(void); const EVP_CIPHER *_libssh2_EVP_aes_192_ctr(void); const EVP_CIPHER *_libssh2_EVP_aes_256_ctr(void); - diff --git a/tests/openssh_server/Dockerfile b/tests/openssh_server/Dockerfile index 2848106..d9f5999 100644 --- a/tests/openssh_server/Dockerfile +++ b/tests/openssh_server/Dockerfile @@ -50,6 +50,10 @@ COPY ssh_host_rsa_key /tmp/etc/ssh/ssh_host_rsa_key RUN mv /tmp/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key RUN chmod 600 /etc/ssh/ssh_host_rsa_key +COPY ssh_host_ecdsa_key /tmp/etc/ssh/ssh_host_ecdsa_key +RUN mv /tmp/etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key +RUN chmod 600 /etc/ssh/ssh_host_ecdsa_key + RUN adduser --disabled-password --gecos 'Test user for libssh2 integration tests' libssh2 RUN echo 'libssh2:my test password' | chpasswd diff --git a/tests/openssh_server/ssh_host_ecdsa_key b/tests/openssh_server/ssh_host_ecdsa_key new file mode 100644 index 0000000..0164b52 --- /dev/null +++ b/tests/openssh_server/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKdqGrp+52U1ehslMI4fX0cmvgHFmKSkMzQGmj6B07ecoAoGCCqGSM49 +AwEHoUQDQgAEL7+zLJ4okP10LZkf1DuIkZF5HhgzetQIyxLKeTJeiN19IKUYIxjs +m9aW3fQRKNi/GhN9JEbHpa9qpgr+8+hhDg== +-----END EC PRIVATE KEY----- diff --git a/tests/test_hostkey.c b/tests/test_hostkey.c index 63c2063..80fc485 100644 --- a/tests/test_hostkey.c +++ b/tests/test_hostkey.c @@ -4,7 +4,7 @@ #include -const char *EXPECTED_HOSTKEY = +const char *EXPECTED_RSA_HOSTKEY = "AAAAB3NzaC1yc2EAAAABIwAAAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKU" "UoR6/Om6cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7B" "Am99fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB" @@ -12,6 +12,10 @@ const char *EXPECTED_HOSTKEY = "i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQKypq7J4iQ" "gkIIbCU1A31+4ExvcIVoxLQw/aTSbw=="; +const char *EXPECTED_ECDSA_HOSTKEY = + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC+/syyeKJD9dC2ZH" + "9Q7iJGReR4YM3rUCMsSynkyXojdfSClGCMY7JvWlt30ESjYvxoTfSRGx6WvaqYK/vPoYQ4="; + int test(LIBSSH2_SESSION *session) { int rc; @@ -26,14 +30,19 @@ int test(LIBSSH2_SESSION *session) return 1; } - if (type != LIBSSH2_HOSTKEY_TYPE_RSA) { - /* Hostkey configured in docker container is RSA */ - fprintf(stderr, "Wrong type of hostkey\n"); + if (type == LIBSSH2_HOSTKEY_TYPE_ECDSA) { + rc = libssh2_base64_decode(session, &expected_hostkey, &expected_len, + EXPECTED_ECDSA_HOSTKEY, strlen(EXPECTED_ECDSA_HOSTKEY)); + } + else if (type == LIBSSH2_HOSTKEY_TYPE_RSA) { + rc = libssh2_base64_decode(session, &expected_hostkey, &expected_len, + EXPECTED_RSA_HOSTKEY, strlen(EXPECTED_RSA_HOSTKEY)); + } + else { + fprintf(stderr, "Unexpected type of hostkey: %i\n", type); return 1; } - rc = libssh2_base64_decode(session, &expected_hostkey, &expected_len, - EXPECTED_HOSTKEY, strlen(EXPECTED_HOSTKEY)); if (rc != 0) { print_last_session_error("libssh2_base64_decode"); return 1; diff --git a/tests/test_hostkey_hash.c b/tests/test_hostkey_hash.c index 6fb78d9..d837600 100644 --- a/tests/test_hostkey_hash.c +++ b/tests/test_hostkey_hash.c @@ -5,7 +5,7 @@ #include -const char *EXPECTED_HOSTKEY = +const char *EXPECTED_RSA_HOSTKEY = "AAAAB3NzaC1yc2EAAAABIwAAAQEArrr/JuJmaZligyfS8vcNur+mWR2ddDQtVdhHzdKU" "UoR6/Om6cvxpe61H1YZO1xCpLUBXmkki4HoNtYOpPB2W4V+8U4BDeVBD5crypEOE1+7B" "Am99fnEDxYIOZq2/jTP0yQmzCpWYS3COyFmkOL7sfX1wQMeW5zQT2WKcxC6FSWbhDqrB" @@ -13,13 +13,27 @@ const char *EXPECTED_HOSTKEY = "i6ELfP3r+q6wdu0P4jWaoo3De1aYxnToV/ldXykpipON4NPamsb6Ph2qlJQKypq7J4iQ" "gkIIbCU1A31+4ExvcIVoxLQw/aTSbw=="; -const char *EXPECTED_MD5_HASH_DIGEST = "0C0ED1A5BB10275F76924CE187CE5C5E"; +const char *EXPECTED_ECDSA_HOSTKEY = + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC+/syyeKJD9dC2ZH" + "9Q7iJGReR4YM3rUCMsSynkyXojdfSClGCMY7JvWlt30ESjYvxoTfSRGx6WvaqYK/vPoYQ4="; -const char *EXPECTED_SHA1_HASH_DIGEST = +const char *EXPECTED_RSA_MD5_HASH_DIGEST = "0C0ED1A5BB10275F76924CE187CE5C5E"; + +const char *EXPECTED_RSA_SHA1_HASH_DIGEST = "F3CD59E2913F4422B80F7B0A82B2B89EAE449387"; +const char *EXPECTED_RSA_SHA256_HASH_DIGEST = "92E3DA49DF3C7F99A828F505ED8239397A5D1F62914459760F878F7510F563A3"; + +const char *EXPECTED_ECDSA_MD5_HASH_DIGEST = "0402E4D897580BBC911379CBD88BCD3D"; + +const char *EXPECTED_ECDSA_SHA1_HASH_DIGEST = + "12FDAD1E3B31B10BABB00F2A8D1B9A62C326BD2F"; + +const char *EXPECTED_ECDSA_SHA256_HASH_DIGEST = "56FCD975B166C3F0342D0036E44C311A86C0EAE40713B53FC776369BAE7F5264"; + const int MD5_HASH_SIZE = 16; const int SHA1_HASH_SIZE = 20; +const int SHA256_HASH_SIZE = 32; static void calculate_digest(const char *hash, size_t hash_len, char *buffer, size_t buffer_len) @@ -39,34 +53,111 @@ int test(LIBSSH2_SESSION *session) const char *md5_hash; const char *sha1_hash; + const char *sha256_hash; + int type; + size_t len; - md5_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); - if (md5_hash == NULL) { - print_last_session_error( - "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_MD5)"); + const char *hostkey = libssh2_session_hostkey(session, &len, &type); + if (hostkey == NULL) { + print_last_session_error("libssh2_session_hostkey"); return 1; } - calculate_digest(md5_hash, MD5_HASH_SIZE, buf, BUFSIZ); + if (type == LIBSSH2_HOSTKEY_TYPE_ECDSA) { - if (strcmp(buf, EXPECTED_MD5_HASH_DIGEST) != 0) { - fprintf(stderr, "MD5 hash not as expected - digest %s != %s\n", buf, - EXPECTED_MD5_HASH_DIGEST); - return 1; - } + md5_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + if (md5_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_MD5)"); + return 1; + } - sha1_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); - if (sha1_hash == NULL) { - print_last_session_error( - "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_SHA1)"); - return 1; - } + calculate_digest(md5_hash, MD5_HASH_SIZE, buf, BUFSIZ); - calculate_digest(sha1_hash, SHA1_HASH_SIZE, buf, BUFSIZ); + if (strcmp(buf, EXPECTED_ECDSA_MD5_HASH_DIGEST) != 0) { + fprintf(stderr, "ECDSA MD5 hash not as expected - digest %s != %s\n", buf, + EXPECTED_ECDSA_MD5_HASH_DIGEST); + return 1; + } - if (strcmp(buf, EXPECTED_SHA1_HASH_DIGEST) != 0) { - fprintf(stderr, "SHA1 hash not as expected - digest %s != %s\n", buf, - EXPECTED_SHA1_HASH_DIGEST); + sha1_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); + if (sha1_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_SHA1)"); + return 1; + } + + calculate_digest(sha1_hash, SHA1_HASH_SIZE, buf, BUFSIZ); + + if (strcmp(buf, EXPECTED_ECDSA_SHA1_HASH_DIGEST) != 0) { + fprintf(stderr, "ECDSA SHA1 hash not as expected - digest %s != %s\n", buf, + EXPECTED_ECDSA_SHA1_HASH_DIGEST); + return 1; + } + + sha256_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); + if (sha256_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_SHA256)"); + return 1; + } + + calculate_digest(sha256_hash, SHA256_HASH_SIZE, buf, BUFSIZ); + + if (strcmp(buf, EXPECTED_ECDSA_SHA256_HASH_DIGEST) != 0) { + fprintf(stderr, "ECDSA SHA256 hash not as expected - digest %s != %s\n", buf, + EXPECTED_ECDSA_SHA256_HASH_DIGEST); + return 1; + } + + } else if ( type == LIBSSH2_HOSTKEY_TYPE_RSA ) { + + md5_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5); + if (md5_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_MD5)"); + return 1; + } + + calculate_digest(md5_hash, MD5_HASH_SIZE, buf, BUFSIZ); + + if (strcmp(buf, EXPECTED_RSA_MD5_HASH_DIGEST) != 0) { + fprintf(stderr, "MD5 hash not as expected - digest %s != %s\n", buf, + EXPECTED_RSA_MD5_HASH_DIGEST); + return 1; + } + + sha1_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1); + if (sha1_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_SHA1)"); + return 1; + } + + calculate_digest(sha1_hash, SHA1_HASH_SIZE, buf, BUFSIZ); + + if (strcmp(buf, EXPECTED_RSA_SHA1_HASH_DIGEST) != 0) { + fprintf(stderr, "SHA1 hash not as expected - digest %s != %s\n", buf, + EXPECTED_RSA_SHA1_HASH_DIGEST); + return 1; + } + + sha256_hash = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA256); + if (sha256_hash == NULL) { + print_last_session_error( + "libssh2_hostkey_hash(LIBSSH2_HOSTKEY_HASH_SHA256)"); + return 1; + } + + calculate_digest(sha256_hash, SHA256_HASH_SIZE, buf, BUFSIZ); + + if (strcmp(buf, EXPECTED_RSA_SHA256_HASH_DIGEST) != 0) { + fprintf(stderr, "SHA256 hash not as expected - digest %s != %s\n", buf, + EXPECTED_RSA_SHA256_HASH_DIGEST); + return 1; + } + } else { + fprintf(stderr, "Unexpected type of hostkey: %i\n", type); return 1; }