From 18cfec8336e88ab7df8a4427b803b9a177053674 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 1 Sep 2014 23:54:05 +0100 Subject: [PATCH] Allow authentication keys to be passed in memory. All credits go to Joe Turpin, I'm just reaplying and cleaning his patch: http://www.libssh2.org/mail/libssh2-devel-archive-2012-01/0015.shtml * Use an unimplemented error for extracting keys from memory with libgcrypt. --- docs/libssh2_userauth_publickey_frommemory.3 | 57 +++++ include/libssh2.h | 10 + src/crypto.h | 16 ++ src/hostkey.c | 66 ++++++ src/libgcrypt.c | 37 +++ src/libssh2_priv.h | 3 + src/openssl.c | 121 ++++++++++ src/userauth.c | 231 +++++++++++++++++++ 8 files changed, 541 insertions(+) create mode 100644 docs/libssh2_userauth_publickey_frommemory.3 diff --git a/docs/libssh2_userauth_publickey_frommemory.3 b/docs/libssh2_userauth_publickey_frommemory.3 new file mode 100644 index 00000000..5e7b34ba --- /dev/null +++ b/docs/libssh2_userauth_publickey_frommemory.3 @@ -0,0 +1,57 @@ +.TH libssh2_userauth_publickey_frommemory 3 "1 Sep 2014" "libssh2 1.5" "libssh2 manual" +.SH NAME +libssh2_userauth_publickey_frommemory - authenticate a session with a public key, read from memory +.SH SYNOPSIS +#include + +.nf +int libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickeydata, + size_t publickeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); +.SH DESCRIPTION +This function allows to authenticate a session with a public key read from memory. +It's only supported when libssh2 is backed by OpenSSL. +\fIsession\fP - Session instance as returned by +.BR libssh2_session_init_ex(3) + +\fIusername\fP - Remote user name to authenticate as. + +\fIusername_len\fP - Length of username. + +\fIpublickeydata\fP - Buffer containing the contents of a public key file. + +\fIpublickeydata_len\fP - Length of public key data. + +\fIprivatekeydata\fP - Buffer containing the contents of a private key file. + +\fIprivatekeydata_len\fP - Length of private key data. + +\fIpassphrase\fP - Passphrase to use when decoding private key file. + +Attempt public key authentication using a PEM encoded private key file stored in memory. + +.SH RETURN VALUE +Return 0 on success or negative on failure. It returns +LIBSSH2_ERROR_EAGAIN when it would otherwise block. While +LIBSSH2_ERROR_EAGAIN is a negative number, it isn't really a failure per se. + +.SH ERRORS +\fILIBSSH2_ERROR_ALLOC\fP - An internal memory allocation call failed. + +\fILIBSSH2_ERROR_SOCKET_SEND\fP - Unable to send data on socket. + +\fILIBSSH2_ERROR_SOCKET_TIMEOUT\fP - + +\fILIBSSH2_ERROR_PUBLICKEY_UNVERIFIED\fP - The username/public key +combination was invalid. + +\fILIBSSH2_ERROR_AUTHENTICATION_FAILED\fP - Authentication using the supplied +public key was not accepted. + +.SH SEE ALSO +.BR libssh2_session_init_ex(3) diff --git a/include/libssh2.h b/include/libssh2.h index e8a04970..e319ad48 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -576,6 +576,16 @@ libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, (username), \ (unsigned int)strlen(username)) +LIBSSH2_API int +libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickeyfiledata, + size_t publickeyfiledata_len, + const char *privatekeyfiledata, + size_t privatekeyfiledata_len, + const char *passphrase); + /* * response_callback is provided with filled by library prompts array, * but client must allocate and fill individual responses. Responses diff --git a/src/crypto.h b/src/crypto.h index a615bb1b..05f5a5c9 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -80,6 +80,10 @@ int _libssh2_rsa_sha1_sign(LIBSSH2_SESSION * session, size_t hash_len, unsigned char **signature, size_t *signature_len); +int _libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase); #if LIBSSH2_DSA int _libssh2_dsa_new(libssh2_dsa_ctx ** dsa, @@ -102,6 +106,10 @@ int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, int _libssh2_dsa_sha1_sign(libssh2_dsa_ctx * dsactx, const unsigned char *hash, unsigned long hash_len, unsigned char *sig); +int _libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase); #endif int _libssh2_cipher_init(_libssh2_cipher_ctx * h, @@ -120,6 +128,14 @@ int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, size_t *pubkeydata_len, const char *privatekey, const char *passphrase); +int _libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase); void _libssh2_init_aes_ctr(void); diff --git a/src/hostkey.c b/src/hostkey.c index 0bbe1528..00677f5c 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -130,6 +130,38 @@ hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION * session, return 0; } +/* + * hostkey_method_ssh_rsa_initPEMFromMemory + * + * Load a Private Key from a memory + */ +static int +hostkey_method_ssh_rsa_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_rsa_ctx *rsactx; + int ret; + + if (*abstract) { + hostkey_method_ssh_rsa_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_rsa_new_private_frommemory(&rsactx, session, + privkeyfiledata, + privkeyfiledata_len, passphrase); + if (ret) { + return -1; + } + + *abstract = rsactx; + + return 0; +} + /* * hostkey_method_ssh_rsa_sign * @@ -208,6 +240,7 @@ static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_rsa = { MD5_DIGEST_LENGTH, hostkey_method_ssh_rsa_init, hostkey_method_ssh_rsa_initPEM, + hostkey_method_ssh_rsa_initPEMFromMemory, hostkey_method_ssh_rsa_sig_verify, hostkey_method_ssh_rsa_signv, NULL, /* encrypt */ @@ -305,6 +338,38 @@ hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION * session, return 0; } +/* + * hostkey_method_ssh_dss_initPEMFromMemory + * + * Load a Private Key from memory + */ +static int +hostkey_method_ssh_dss_initPEMFromMemory(LIBSSH2_SESSION * session, + const char *privkeyfiledata, + size_t privkeyfiledata_len, + unsigned const char *passphrase, + void **abstract) +{ + libssh2_dsa_ctx *dsactx; + int ret; + + if (*abstract) { + hostkey_method_ssh_dss_dtor(session, abstract); + *abstract = NULL; + } + + ret = _libssh2_dsa_new_private_frommemory(&dsactx, session, + privkeyfiledata, + privkeyfiledata_len, passphrase); + if (ret) { + return -1; + } + + *abstract = dsactx; + + return 0; +} + /* * libssh2_hostkey_method_ssh_dss_sign * @@ -391,6 +456,7 @@ static const LIBSSH2_HOSTKEY_METHOD hostkey_method_ssh_dss = { MD5_DIGEST_LENGTH, hostkey_method_ssh_dss_init, hostkey_method_ssh_dss_initPEM, + hostkey_method_ssh_dss_initPEMFromMemory, hostkey_method_ssh_dss_sig_verify, hostkey_method_ssh_dss_signv, NULL, /* encrypt */ diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 7d09ffb4..e85aecde 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -149,6 +149,17 @@ _libssh2_dsa_new(libssh2_dsa_ctx ** dsactx, return 0; } +int +_libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unable to extract private key from memory: " + "Method unimplemented in libgcrypt backend"); +} + int _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, LIBSSH2_SESSION * session, @@ -251,6 +262,17 @@ _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, return ret; } +int +_libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unable to extract private key from memory: " + "Method unimplemented in libgcrypt backend"); +} + int _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, LIBSSH2_SESSION * session, @@ -566,6 +588,21 @@ _libssh2_cipher_crypt(_libssh2_cipher_ctx * ctx, return ret; } +int +_libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NOT_SUPPORTED, + "Unable to extract public key from private key in memory: " + "Method unimplemented in libgcrypt backend"); +} + int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, unsigned char **method, diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h index 10d7eb04..6007e7ba 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -854,6 +854,9 @@ struct _LIBSSH2_HOSTKEY_METHOD size_t hostkey_data_len, void **abstract); int (*initPEM) (LIBSSH2_SESSION * session, const char *privkeyfile, unsigned const char *passphrase, void **abstract); + int (*initPEMFromMemory) (LIBSSH2_SESSION * session, + const char *privkeyfiledata, size_t privkeyfiledata_len, + unsigned const char *passphrase, void **abstract); int (*sig_verify) (LIBSSH2_SESSION * session, const unsigned char *sig, size_t sig_len, const unsigned char *m, size_t m_len, void **abstract); diff --git a/src/openssl.c b/src/openssl.c index 056b0b78..2e314723 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -387,6 +387,28 @@ passphrase_cb(char *buf, int size, int rwflag, char *passphrase) typedef void * (*pem_read_bio_func)(BIO *, void **, pem_password_cb *, void * u); +static int +read_private_key_from_memory(void ** key_ctx, + pem_read_bio_func read_private_key, + const char * filedata, + size_t filedata_len, + unsigned const char *passphrase) +{ + BIO * bp; + + *key_ctx = NULL; + + bp = BIO_new_mem_buf(filedata, filedata_len); + if (!bp) { + return -1; + } + *key_ctx = read_private_key(bp, NULL, (pem_password_cb *) passphrase_cb, + (void *) passphrase); + + BIO_free(bp); + return (*key_ctx) ? 0 : -1; +} + static int read_private_key_from_file(void ** key_ctx, pem_read_bio_func read_private_key, @@ -409,6 +431,22 @@ read_private_key_from_file(void ** key_ctx, return (*key_ctx) ? 0 : -1; } + int +_libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase) +{ + pem_read_bio_func read_rsa = + (pem_read_bio_func) &PEM_read_bio_RSAPrivateKey; + (void) session; + + _libssh2_init_if_needed(); + + return read_private_key_from_memory((void **) rsa, read_rsa, + filedata, filedata_len, passphrase); +} + int _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, LIBSSH2_SESSION * session, @@ -425,6 +463,22 @@ _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, } #if LIBSSH2_DSA +int +_libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filedata, size_t filedata_len, + unsigned const char *passphrase) +{ + pem_read_bio_func read_dsa = + (pem_read_bio_func) &PEM_read_bio_DSAPrivateKey; + (void) session; + + _libssh2_init_if_needed(); + + return read_private_key_from_memory((void **) dsa, read_dsa, + filedata, filedata_len, passphrase); +} + int _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, LIBSSH2_SESSION * session, @@ -817,4 +871,71 @@ _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, return st; } +int +_libssh2_pub_priv_keyfilememory(LIBSSH2_SESSION *session, + unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + int st; + BIO* bp; + EVP_PKEY* pk; + + _libssh2_debug(session, + LIBSSH2_TRACE_AUTH, + "Computing public key from private key."); + + bp = BIO_new_mem_buf(privatekeydata, privatekeydata_len); + if (!bp) { + 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) { + return _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file: " + "Wrong passphrase or invalid/unrecognized " + "private key file format"); + } + + 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 = _libssh2_error(session, + LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key file: " + "Unsupported private key file format"); + break; + } + + EVP_PKEY_free(pk); + return st; +} + #endif /* LIBSSH2_OPENSSL */ diff --git a/src/userauth.c b/src/userauth.c index d7e9d9db..e872385c 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -442,6 +442,76 @@ libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, return rc; } +static int +memory_read_publickey(LIBSSH2_SESSION * session, unsigned char **method, + size_t *method_len, + unsigned char **pubkeydata, + size_t *pubkeydata_len, + const char *pubkeyfiledata, + size_t pubkeyfiledata_len) +{ + unsigned char *pubkey = NULL, *sp1, *sp2, *tmp; + size_t pubkey_len = pubkeyfiledata_len; + unsigned int tmp_len; + + if (pubkeyfiledata_len <= 1) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public key file"); + } + + pubkey = LIBSSH2_ALLOC(session, pubkeyfiledata_len); + if (!pubkey) { + return _libssh2_error(session, LIBSSH2_ERROR_ALLOC, + "Unable to allocate memory for public key data"); + } + + memcpy(pubkey, pubkeyfiledata, pubkeyfiledata_len); + + /* + * Remove trailing whitespace + */ + while (pubkey_len && isspace(pubkey[pubkey_len - 1])) + pubkey_len--; + + if (!pubkey_len) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Missing public key data"); + } + + if ((sp1 = memchr(pubkey, ' ', pubkey_len)) == NULL) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid public key data"); + } + + sp1++; + + if ((sp2 = memchr(sp1, ' ', pubkey_len - (sp1 - pubkey - 1))) == NULL) { + /* Assume that the id string is missing, but that it's okay */ + sp2 = pubkey + pubkey_len; + } + + if (libssh2_base64_decode(session, (char **) &tmp, &tmp_len, + (char *) sp1, sp2 - sp1)) { + LIBSSH2_FREE(session, pubkey); + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid key data, not base64 encoded"); + } + + /* Wasting some bytes here (okay, more than some), but since it's likely + * to be freed soon anyway, we'll just avoid the extra free/alloc and call + * it a wash + */ + *method = pubkey; + *method_len = sp1 - pubkey - 1; + + *pubkeydata = tmp; + *pubkeydata_len = tmp_len; + + return 0; +} + /* * file_read_publickey * @@ -547,7 +617,43 @@ file_read_publickey(LIBSSH2_SESSION * session, unsigned char **method, return 0; } +static int +memory_read_privatekey(LIBSSH2_SESSION * session, + const LIBSSH2_HOSTKEY_METHOD ** hostkey_method, + void **hostkey_abstract, + const unsigned char *method, int method_len, + const char *privkeyfiledata, size_t privkeyfiledata_len, + const char *passphrase) +{ + const LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = + libssh2_hostkey_methods(); + *hostkey_method = NULL; + *hostkey_abstract = NULL; + while (*hostkey_methods_avail && (*hostkey_methods_avail)->name) { + if ((*hostkey_methods_avail)->initPEMFromMemory + && strncmp((*hostkey_methods_avail)->name, (const char *) method, + method_len) == 0) { + *hostkey_method = *hostkey_methods_avail; + break; + } + hostkey_methods_avail++; + } + if (!*hostkey_method) { + return _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, + "No handler for specified private key"); + } + + if ((*hostkey_method)-> + initPEMFromMemory(session, privkeyfiledata, privkeyfiledata_len, + (unsigned char *) passphrase, + hostkey_abstract)) { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to initialize private key from file"); + } + + return 0; +} /* libssh2_file_read_privatekey * Read a PEM encoded private key from an id_??? style file @@ -595,6 +701,42 @@ struct privkey_file { const char *passphrase; }; +static int +sign_frommemory(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, + const unsigned char *data, size_t data_len, void **abstract) +{ + struct privkey_file *pk_file = (struct privkey_file *) (*abstract); + const LIBSSH2_HOSTKEY_METHOD *privkeyobj; + void *hostkey_abstract; + struct iovec datavec; + int rc; + + rc = memory_read_privatekey(session, &privkeyobj, &hostkey_abstract, + session->userauth_pblc_method, + session->userauth_pblc_method_len, + pk_file->filename, + strlen(pk_file->filename), + pk_file->passphrase); + if(rc) + return rc; + + datavec.iov_base = (void *)data; + datavec.iov_len = data_len; + + if (privkeyobj->signv(session, sig, sig_len, 1, &datavec, + &hostkey_abstract)) { + if (privkeyobj->dtor) { + privkeyobj->dtor(session, abstract); + } + return -1; + } + + if (privkeyobj->dtor) { + privkeyobj->dtor(session, &hostkey_abstract); + } + return 0; +} + static int sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, const unsigned char *data, size_t data_len, void **abstract) @@ -1215,6 +1357,65 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, "username/public key combination"); } + /* + * userauth_publickey_frommemory + * Authenticate using a keypair from memory + */ +static int +userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + size_t username_len, + const char *publickeydata, + size_t publickeydata_len, + const char *privatekeydata, + size_t privatekeydata_len, + const char *passphrase) +{ + unsigned char *pubkeydata = NULL; + size_t pubkeydata_len = 0; + struct privkey_file privkey_file; + void *abstract = &privkey_file; + int rc; + + privkey_file.filename = privatekeydata; + privkey_file.passphrase = passphrase; + + if (session->userauth_pblc_state == libssh2_NB_state_idle) { + if (publickeydata_len && publickeydata) { + rc = memory_read_publickey(session, &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + publickeydata, publickeydata_len); + if(rc) + return rc; + } + else if (privatekeydata_len && privatekeydata) { + /* Compute public key from private key. */ + if (_libssh2_pub_priv_keyfilememory(session, + &session->userauth_pblc_method, + &session->userauth_pblc_method_len, + &pubkeydata, &pubkeydata_len, + privatekeydata, privatekeydata_len, + passphrase)) + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Unable to extract public key " + "from private key."); + } + else { + return _libssh2_error(session, LIBSSH2_ERROR_FILE, + "Invalid data in public and private key."); + } + } + + rc = _libssh2_userauth_publickey(session, username, username_len, + pubkeydata, pubkeydata_len, + sign_frommemory, &abstract); + if(pubkeydata) + LIBSSH2_FREE(session, pubkeydata); + + return rc; +} + /* * userauth_publickey_fromfile * Authenticate using a keypair found in the named files @@ -1267,6 +1468,36 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session, return rc; } +/* libssh2_userauth_publickey_frommemory + * Authenticate using a keypair from memory + */ +LIBSSH2_API int +libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *user, + size_t user_len, + const char *publickeyfiledata, + size_t publickeyfiledata_len, + const char *privatekeyfiledata, + size_t privatekeyfiledata_len, + const char *passphrase) +{ + int rc; + + if(NULL == passphrase) + /* if given a NULL pointer, make it point to a zero-length + string to save us from having to check this all over */ + passphrase=""; + + BLOCK_ADJUST(rc, session, + userauth_publickey_frommemory(session, user, user_len, + publickeyfiledata, + publickeyfiledata_len, + privatekeyfiledata, + privatekeyfiledata_len, + passphrase)); + return rc; +} + /* libssh2_userauth_publickey_fromfile_ex * Authenticate using a keypair found in the named files */