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.
diff --git a/docs/libssh2_userauth_publickey_frommemory.3 b/docs/libssh2_userauth_publickey_frommemory.3
new file mode 100644
index 0000000..5e7b34b
--- /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 <libssh2.h>
+
+.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 e8a0497..e319ad4 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -576,6 +576,16 @@
(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 a615bb1..05f5a5c 100644
--- a/src/crypto.h
+++ b/src/crypto.h
@@ -80,6 +80,10 @@
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_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 @@
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 0bbe152..00677f5 100644
--- a/src/hostkey.c
+++ b/src/hostkey.c
@@ -131,6 +131,38 @@
}
/*
+ * 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 +240,7 @@
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 +339,38 @@
}
/*
+ * 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
@@ -391,6 +456,7 @@
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 7d09ffb..e85aecd 100644
--- a/src/libgcrypt.c
+++ b/src/libgcrypt.c
@@ -150,6 +150,17 @@
}
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,
const char *filename, unsigned const char *passphrase)
@@ -252,6 +263,17 @@
}
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,
const char *filename, unsigned const char *passphrase)
@@ -567,6 +589,21 @@
}
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,
size_t *method_len,
diff --git a/src/libssh2_priv.h b/src/libssh2_priv.h
index 10d7eb0..6007e7b 100644
--- a/src/libssh2_priv.h
+++ b/src/libssh2_priv.h
@@ -854,6 +854,9 @@
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 056b0b7..2e31472 100644
--- a/src/openssl.c
+++ b/src/openssl.c
@@ -388,6 +388,28 @@
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,
const char * filename,
@@ -409,6 +431,22 @@
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,
@@ -426,6 +464,22 @@
#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)
@@ -817,4 +871,71 @@
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 d7e9d9d..e872385 100644
--- a/src/userauth.c
+++ b/src/userauth.c
@@ -442,6 +442,76 @@
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 @@
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
@@ -596,6 +702,42 @@
};
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 @@
"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 @@
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
*/