From 5b1a7ec2f139d13b8213fc5a5b87f05ea3a30a40 Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Sat, 18 Dec 2010 23:38:08 +0100 Subject: [PATCH] userauth: derive publickey from private Pass a NULL pointer for the publickey parameter of libssh2_userauth_publickey_fromfile and libssh2_userauth_hostbased_fromfile functions. In this case, the functions recompute the public key from the private key file data. This is work done by Jean-Louis CHARTON , then adapted by Mark Smith and slightly edited further by me Daniel. WARNING: this does leave the feature NOT WORKING when libssh2 is built to use libgcrypt instead of OpenSSL simply due to lack of implementation. --- src/libgcrypt.c | 13 +++ src/libgcrypt.h | 8 ++ src/openssl.c | 292 ++++++++++++++++++++++++++++++++++++++++++++++-- src/openssl.h | 8 ++ src/userauth.c | 52 ++++++--- 5 files changed, 348 insertions(+), 25 deletions(-) diff --git a/src/libgcrypt.c b/src/libgcrypt.c index b36c78b..03e3f7f 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -572,4 +572,17 @@ _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, return ret; } +int +_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase) +{ + return -1; /* not yet supported; interpreted by userauth.c to call + libssh2_error */ +} + #endif /* LIBSSH2_LIBGCRYPT */ diff --git a/src/libgcrypt.h b/src/libgcrypt.h index d925700..f273d46 100644 --- a/src/libgcrypt.h +++ b/src/libgcrypt.h @@ -207,3 +207,11 @@ int _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, #define _libssh2_bn_bytes(bn) (gcry_mpi_get_nbits (bn) / 8 + ((gcry_mpi_get_nbits (bn) % 8 == 0) ? 0 : 1)) #define _libssh2_bn_bits(bn) gcry_mpi_get_nbits (bn) #define _libssh2_bn_free(bn) gcry_mpi_release(bn) + +int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase); diff --git a/src/openssl.c b/src/openssl.c index afcd33b..cc31887 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -225,17 +225,17 @@ aes_ctr_init(EVP_CIPHER_CTX *ctx, const unsigned char *key, return 0; switch (ctx->key_len) { - case 16: - aes_cipher = EVP_aes_128_ecb(); - break; - case 24: - aes_cipher = EVP_aes_192_ecb(); - break; - case 32: - aes_cipher = EVP_aes_256_ecb(); - break; - default: - return 0; + case 16: + aes_cipher = EVP_aes_128_ecb(); + break; + case 24: + aes_cipher = EVP_aes_192_ecb(); + break; + case 32: + aes_cipher = EVP_aes_256_ecb(); + break; + default: + return 0; } c->aes_ctx = malloc(sizeof(EVP_CIPHER_CTX)); if (c->aes_ctx == NULL) @@ -515,4 +515,274 @@ libssh2_md5(const unsigned char *message, unsigned long len, EVP_DigestFinal(&ctx, out, NULL); } +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) +{ + int e_bytes, n_bytes; + unsigned long len; + unsigned char* key; + unsigned char* p; + + e_bytes = BN_num_bytes(rsa->e) + 1; + n_bytes = BN_num_bytes(rsa->n) + 1; + + /* Key form is "ssh-rsa" + e + n. */ + len = 4 + 7 + 4 + e_bytes + 4 + n_bytes; + + key = LIBSSH2_ALLOC(session, len); + if (key == NULL) { + return NULL; + } + + /* Process key encoding. */ + p = key; + + _libssh2_htonu32(p, 7); /* Key type. */ + p += 4; + memcpy(p, "ssh-rsa", 7); + p += 7; + + p = write_bn(p, rsa->e, e_bytes); + p = write_bn(p, rsa->n, n_bytes); + + *key_len = (size_t)(p - key); + return key; +} + +static unsigned char * +gen_publickey_from_dsa(LIBSSH2_SESSION* session, DSA *dsa, + size_t *key_len) +{ + int p_bytes, q_bytes, g_bytes, k_bytes; + unsigned long len; + unsigned char* key; + unsigned char* p; + + p_bytes = BN_num_bytes(dsa->p) + 1; + q_bytes = BN_num_bytes(dsa->q) + 1; + g_bytes = BN_num_bytes(dsa->g) + 1; + k_bytes = BN_num_bytes(dsa->pub_key) + 1; + + /* Key form is "ssh-dss" + p + q + g + pub_key. */ + len = 4 + 7 + 4 + p_bytes + 4 + q_bytes + 4 + g_bytes + 4 + k_bytes; + + key = LIBSSH2_ALLOC(session, len); + if (key == NULL) { + return NULL; + } + + /* Process key encoding. */ + p = key; + + _libssh2_htonu32(p, 7); /* Key type. */ + p += 4; + memcpy(p, "ssh-dss", 7); + p += 7; + + p = write_bn(p, dsa->p, p_bytes); + p = write_bn(p, dsa->q, q_bytes); + p = write_bn(p, dsa->g, g_bytes); + p = write_bn(p, dsa->pub_key, k_bytes); + + *key_len = (size_t)(p - key); + return key; +} + +static int +gen_publickey_from_rsa_evp(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + EVP_PKEY *pk) +{ + RSA* rsa = NULL; + unsigned char* key; + unsigned char* method_buf = NULL; + size_t key_len; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from RSA private key envelop"); + + rsa = EVP_PKEY_get1_RSA(pk); + if (rsa == NULL) { + /* Assume memory allocation error... what else could it be ? */ + goto __alloc_error; + } + + method_buf = LIBSSH2_ALLOC(session, 7); /* ssh-rsa. */ + if (method_buf == NULL) { + goto __alloc_error; + } + + key = gen_publickey_from_rsa(session, rsa, &key_len); + if (key == NULL) { + goto __alloc_error; + } + RSA_free(rsa); + + memcpy(method_buf, "ssh-rsa", 7); + *method = method_buf; + *method_len = 7; + *pubkeydata = key; + *pubkeydata_len = key_len; + return 0; + + __alloc_error: + if (rsa != NULL) { + RSA_free(rsa); + } + if (method_buf != NULL) { + LIBSSH2_FREE(session, method_buf); + } + + _libssh2_error(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for private key data"); + return -1; +} + +static int +gen_publickey_from_dsa_evp(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + EVP_PKEY *pk) +{ + DSA* dsa = NULL; + unsigned char* key; + unsigned char* method_buf = NULL; + size_t key_len; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from DSA private key envelop"); + + dsa = EVP_PKEY_get1_DSA(pk); + if (dsa == NULL) { + /* Assume memory allocation error... what else could it be ? */ + goto __alloc_error; + } + + method_buf = LIBSSH2_ALLOC(session, 7); /* ssh-dss. */ + if (method_buf == NULL) { + goto __alloc_error; + } + + key = gen_publickey_from_dsa(session, dsa, &key_len); + if (key == NULL) { + goto __alloc_error; + } + DSA_free(dsa); + + memcpy(method_buf, "ssh-dss", 7); + *method = method_buf; + *method_len = 7; + *pubkeydata = key; + *pubkeydata_len = key_len; + return 0; + + __alloc_error: + if (dsa != NULL) { + DSA_free(dsa); + } + if (method_buf != NULL) { + LIBSSH2_FREE(session, method_buf); + } + + _libssh2_error(session, + LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for private key data"); + return -1; +} + +int +_libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase) +{ + int st; + BIO* bp; + EVP_PKEY* pk; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from private key file: %s", + privatekey); + + bp = BIO_new_file(privatekey, "r"); + if (bp == NULL) { + _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to open private key file"); + return -1; + } + if (!EVP_get_cipherbyname("des")) { + /* If this cipher isn't loaded it's a pretty good indication that none + * are. I have *NO DOUBT* that there's a better way to deal with this + * ($#&%#$(%$#( Someone buy me an OpenSSL manual and I'll read up on + * it. + */ + OpenSSL_add_all_ciphers(); + } + BIO_reset(bp); + pk = PEM_read_bio_PrivateKey(bp, NULL, NULL, (void*)passphrase); + BIO_free(bp); + + if (pk == NULL) { + _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Wrong passphrase or invalid/unrecognized " + "private key file format"); + return -1; + } + + switch (pk->type) { + case EVP_PKEY_RSA : + st = gen_publickey_from_rsa_evp( + session, method, method_len, pubkeydata, pubkeydata_len, pk); + break; + + case EVP_PKEY_DSA : + st = gen_publickey_from_dsa_evp( + session, method, method_len, pubkeydata, pubkeydata_len, pk); + break; + + default : + st = -1; + _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unsupported private key file format"); + break; + } + + EVP_PKEY_free(pk); + return st; +} + #endif /* !LIBSSH2_LIBGCRYPT */ diff --git a/src/openssl.h b/src/openssl.h index 8e35f9d..5aa7049 100644 --- a/src/openssl.h +++ b/src/openssl.h @@ -241,3 +241,11 @@ int _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, 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); + +int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekey, + const char *passphrase); diff --git a/src/userauth.c b/src/userauth.c index de29dc2..976d5fa 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -655,13 +655,25 @@ userauth_hostbased_fromfile(LIBSSH2_SESSION *session, memset(&session->userauth_host_packet_requirev_state, 0, sizeof(session->userauth_host_packet_requirev_state)); - rc = file_read_publickey(session, &session->userauth_host_method, - &session->userauth_host_method_len, - &pubkeydata, &pubkeydata_len, - publickey); - if(rc) - /* Note: file_read_publickey() calls _libssh2_error() */ - return rc; + if (publickey) { + rc = file_read_publickey(session, &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, publickey); + if(rc) + /* Note: file_read_publickey() calls _libssh2_error() */ + return rc; + } + else { + /* Compute public key from private key. */ + if (_libssh2_pub_priv_keyfile(session, + &session->userauth_host_method, + &session->userauth_host_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase)) + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file"); + } /* * 52 = packet_type(1) + username_len(4) + servicename_len(4) + @@ -932,7 +944,7 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, else if (session->userauth_pblc_method_len != _libssh2_ntohu32(pubkeydata)) return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, - "Invalid public key"); + "Invalid public key"); /* * 45 = packet_type(1) + username_len(4) + servicename_len(4) + @@ -1221,11 +1233,24 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session, privkey_file.passphrase = passphrase; if (session->userauth_pblc_state == libssh2_NB_state_idle) { - rc = file_read_publickey(session, &session->userauth_pblc_method, - &session->userauth_pblc_method_len, - &pubkeydata, &pubkeydata_len, publickey); - if(rc) - return rc; + if (publickey) { + rc = file_read_publickey(session, &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len,publickey); + if(rc) + return rc; + } + else { + /* Compute public key from private key. */ + if (_libssh2_pub_priv_keyfile(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + privatekey, passphrase)) + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file"); + } } rc = _libssh2_userauth_publickey(session, username, username_len, @@ -1643,4 +1668,3 @@ libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, response_callback)); return rc; } -