Add a new user authentication API function that allows the client to
pass a buffer containting the contents of the authentication key files.
--- docs/libssh2_userauth_publickey_frommemory.3 | 55 ++++++ include/libssh2.h | 10 + src/crypto.h | 19 ++ src/hostkey.c | 68 ++++++++ src/libssh2_priv.h | 4 + src/openssl.c | 120 ++++++++++++++ src/userauth.c | 228 ++++++++++++++++++++++++++ 7 files changed, 504 insertions(+), 0 deletions(-) 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 0000000..b217332 --- /dev/null +++ b/docs/libssh2_userauth_publickey_frommemory.3 @@ -0,0 +1,55 @@ +.TH libssh2_userauth_publickey_frommemory 3 "18 Jan 2012" "libssh2 1.3" "libssh2 manual" +.SH NAME +libssh2_userauth_publickey_frommemory - authenticate a session with a public key, read from memory +.SH SYNOPSIS +#include <libssh2.h> + +.nf +int libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickeydata, + size_t publickeydata_len + const char *privatekeydata, + size_t privatekeydata_len + const char *passphrase); +.SH DESCRIPTION +\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 5a22be2..ac1bfed 100644 --- a/include/libssh2.h +++ b/include/libssh2.h @@ -531,6 +531,16 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, const char *privatekey, const char *passphrase); +LIBSSH2_API int +libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, + const char *username, + unsigned int username_len, + const char *publickeyfiledata, + size_t publickeyfiledata_len, + const char *privatekeyfiledata, + size_t privatekeyfiledata_len, + const char *passphrase); + #define libssh2_userauth_publickey_fromfile(session, username, publickey, \ privatekey, passphrase) \ libssh2_userauth_publickey_fromfile_ex((session), (username), \ diff --git a/src/crypto.h b/src/crypto.h index 8cf34f5..6e95e9a 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -64,6 +64,11 @@ int _libssh2_rsa_new_private(libssh2_rsa_ctx ** rsa, LIBSSH2_SESSION * session, const char *filename, unsigned const char *passphrase); +int _libssh2_rsa_new_private_frommemory(libssh2_rsa_ctx ** rsa, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); int _libssh2_rsa_sha1_verify(libssh2_rsa_ctx * rsa, const unsigned char *sig, unsigned long sig_len, @@ -89,6 +94,11 @@ int _libssh2_dsa_new_private(libssh2_dsa_ctx ** dsa, LIBSSH2_SESSION * session, const char *filename, unsigned const char *passphrase); +int _libssh2_dsa_new_private_frommemory(libssh2_dsa_ctx ** dsa, + LIBSSH2_SESSION * session, + const char *filedata, + size_t filedata_len, + unsigned const char *passphrase); int _libssh2_dsa_sha1_verify(libssh2_dsa_ctx * dsactx, const unsigned char *sig, const unsigned char *m, unsigned long m_len); @@ -113,6 +123,15 @@ int _libssh2_pub_priv_keyfile(LIBSSH2_SESSION *session, 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); #endif diff --git a/src/hostkey.c b/src/hostkey.c index 53f7479..69dbfb8 100644 --- a/src/hostkey.c +++ b/src/hostkey.c @@ -131,6 +131,39 @@ hostkey_method_ssh_rsa_initPEM(LIBSSH2_SESSION * session, } /* + * 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 * * Verify signature created by remote @@ -208,6 +241,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 */ @@ -306,6 +340,39 @@ hostkey_method_ssh_dss_initPEM(LIBSSH2_SESSION * session, } /* + * 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 * * Verify signature created by remote @@ -392,6 +459,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/libssh2_priv.h b/src/libssh2_priv.h index c670a16..6579b77 100644 --- a/src/libssh2_priv.h +++ b/src/libssh2_priv.h @@ -853,6 +853,10 @@ 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 481982c..3daf74a 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -386,6 +386,27 @@ 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((void*)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, @@ -410,6 +431,22 @@ read_private_key_from_file(void ** key_ctx, } 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, const char *filename, unsigned const char *passphrase) @@ -426,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, const char *filename, unsigned const char *passphrase) @@ -801,4 +854,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((void*)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_LIBGCRYPT */ diff --git a/src/userauth.c b/src/userauth.c index 3fcb200..c4b71f4 100644 --- a/src/userauth.c +++ b/src/userauth.c @@ -441,6 +441,74 @@ 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 * @@ -543,7 +611,42 @@ 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 @@ -586,12 +689,49 @@ file_read_privatekey(LIBSSH2_SESSION * session, return 0; } + struct privkey_file { const char *filename; 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) { @@ -1212,6 +1352,64 @@ _libssh2_userauth_publickey(LIBSSH2_SESSION *session, } /* + * 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 */ @@ -1263,6 +1461,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, + unsigned int 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 */ -- 1.7.5.4 _______________________________________________ libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-develReceived on 2012-03-19