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
Этот коммит содержится в:
родитель
bcd492163b
Коммит
aba34f5f56
@ -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
|
||||
|
47
src/crypto.h
47
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,
|
||||
|
314
src/hostkey.c
314
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;
|
||||
}
|
||||
|
||||
|
868
src/kex.c
868
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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
649
src/openssl.c
649
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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
5
tests/openssh_server/ssh_host_ecdsa_key
Обычный файл
5
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-----
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
Загрузка…
x
Ссылка в новой задаче
Block a user