From f84db7d166fb76f33b879ae297a1899a12b80b57 Mon Sep 17 00:00:00 2001 From: Joshua Roys Date: Wed, 15 Dec 2021 09:35:46 -0500 Subject: [PATCH] GUACAMOLE-745: Support OpenSSH private keys & ED25519 Let libssh2 parse PEM and ssh-native keys. Requires libssh2 1.9.0+ compiled against a crypto backend supporting ed25519. --- configure.ac | 31 +-- src/common-ssh/Makefile.am | 4 - src/common-ssh/common-ssh/dsa-compat.h | 61 ------ src/common-ssh/common-ssh/key.h | 102 ++------- src/common-ssh/common-ssh/rsa-compat.h | 40 ---- src/common-ssh/dsa-compat.c | 59 ------ src/common-ssh/key.c | 283 +++++++++---------------- src/common-ssh/rsa-compat.c | 38 ---- src/common-ssh/ssh.c | 58 +---- src/libguac/guacamole/string.h | 22 ++ src/libguac/string.c | 32 +++ src/libguac/tests/Makefile.am | 1 + src/libguac/tests/string/strnstr.c | 73 +++++++ 13 files changed, 256 insertions(+), 548 deletions(-) delete mode 100644 src/common-ssh/common-ssh/dsa-compat.h delete mode 100644 src/common-ssh/common-ssh/rsa-compat.h delete mode 100644 src/common-ssh/dsa-compat.c delete mode 100644 src/common-ssh/rsa-compat.c create mode 100644 src/libguac/tests/string/strnstr.c diff --git a/configure.ac b/configure.ac index 5e1c4021..17f56a35 100644 --- a/configure.ac +++ b/configure.ac @@ -160,6 +160,11 @@ AC_CHECK_DECL([strlcat], [Whether strlcat() is defined])],, [#include ]) +AC_CHECK_DECL([strnstr], + [AC_DEFINE([HAVE_STRNSTR],, + [Whether strnstr() is defined])],, + [#include ]) + # Typedefs AC_TYPE_SIZE_T AC_TYPE_SSIZE_T @@ -320,30 +325,6 @@ then else AC_DEFINE([ENABLE_SSL],, [Whether SSL-related support is enabled]) - # OpenSSL 1.1 accessor function for DSA signature values - AC_CHECK_DECL([DSA_SIG_get0], - [AC_DEFINE([HAVE_DSA_SIG_GET0],, - [Whether libssl provides DSA_SIG_get0()])],, - [#include ]) - - # OpenSSL 1.1 accessor function for DSA public key parameters - AC_CHECK_DECL([DSA_get0_pqg], - [AC_DEFINE([HAVE_DSA_GET0_PQG],, - [Whether libssl provides DSA_get0_pqg()])],, - [#include ]) - - # OpenSSL 1.1 accessor function for DSA public/private key values - AC_CHECK_DECL([DSA_get0_key], - [AC_DEFINE([HAVE_DSA_GET0_KEY],, - [Whether libssl provides DSA_get0_key()])],, - [#include ]) - - # OpenSSL 1.1 accessor function for RSA public/private key values - AC_CHECK_DECL([RSA_get0_key], - [AC_DEFINE([HAVE_RSA_GET0_KEY],, - [Whether libssl provides RSA_get0_key()])],, - [#include ]) - # OpenSSL 1.1 does away with explicit threading callbacks AC_MSG_CHECKING([whether libssl requires threading callbacks]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ @@ -932,7 +913,7 @@ if test "x$with_ssh" != "xno" then have_libssh2=yes - AC_CHECK_LIB([ssh2], [libssh2_session_init_ex], + AC_CHECK_LIB([ssh2], [libssh2_userauth_publickey_frommemory], [SSH_LIBS="$SSH_LIBS -lssh2"], [have_libssh2=no]) fi diff --git a/src/common-ssh/Makefile.am b/src/common-ssh/Makefile.am index 8402e5b0..5d1a88d1 100644 --- a/src/common-ssh/Makefile.am +++ b/src/common-ssh/Makefile.am @@ -31,8 +31,6 @@ SUBDIRS = . tests libguac_common_ssh_la_SOURCES = \ buffer.c \ - dsa-compat.c \ - rsa-compat.c \ sftp.c \ ssh.c \ key.c \ @@ -40,8 +38,6 @@ libguac_common_ssh_la_SOURCES = \ noinst_HEADERS = \ common-ssh/buffer.h \ - common-ssh/dsa-compat.h \ - common-ssh/rsa-compat.h \ common-ssh/key.h \ common-ssh/sftp.h \ common-ssh/ssh.h \ diff --git a/src/common-ssh/common-ssh/dsa-compat.h b/src/common-ssh/common-ssh/dsa-compat.h deleted file mode 100644 index 9bc4f8a7..00000000 --- a/src/common-ssh/common-ssh/dsa-compat.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef GUAC_COMMON_SSH_DSA_COMPAT_H -#define GUAC_COMMON_SSH_DSA_COMPAT_H - -#include "config.h" - -#include -#include - -#ifndef HAVE_DSA_GET0_PQG -/** - * DSA_get0_pqg() implementation for versions of OpenSSL which lack this - * function (pre 1.1). - * - * See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_pqg.html - */ -void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p, - const BIGNUM** q, const BIGNUM** g); -#endif - -#ifndef HAVE_DSA_GET0_KEY -/** - * DSA_get0_key() implementation for versions of OpenSSL which lack this - * function (pre 1.1). - * - * See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_get0_key.html - */ -void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key, - const BIGNUM** priv_key); -#endif - -#ifndef HAVE_DSA_SIG_GET0 -/** - * DSA_SIG_get0() implementation for versions of OpenSSL which lack this - * function (pre 1.1). - * - * See: https://www.openssl.org/docs/man1.1.0/crypto/DSA_SIG_get0.html - */ -void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s); -#endif - -#endif - diff --git a/src/common-ssh/common-ssh/key.h b/src/common-ssh/common-ssh/key.h index 897555a3..5d829b9a 100644 --- a/src/common-ssh/common-ssh/key.h +++ b/src/common-ssh/common-ssh/key.h @@ -25,75 +25,27 @@ #include #include -#include +/** + * OpenSSH v1 private keys are PEM-wrapped base64-encoded blobs. The encoded data begins with: + * "openssh-key-v1\0" + */ +#define OPENSSH_V1_KEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEA" /** - * The expected header of RSA private keys. + * The base64-encoded prefix indicating an OpenSSH v1 private key is NOT protected by a + * passphrase. Specifically, it is the following data fields and values: + * pascal string: cipher name ("none") + * pascal string: kdf name ("none") + * pascal string: kdf params (NULL) + * 32-bit int: number of keys (1) */ -#define SSH_RSA_KEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" - -/** - * The expected header of DSA private keys. - */ -#define SSH_DSA_KEY_HEADER "-----BEGIN DSA PRIVATE KEY-----" - -/** - * The size of single number within a DSA signature, in bytes. - */ -#define DSA_SIG_NUMBER_SIZE 20 - -/** - * The size of a DSA signature, in bytes. - */ -#define DSA_SIG_SIZE DSA_SIG_NUMBER_SIZE*2 - -/** - * The type of an SSH key. - */ -typedef enum guac_common_ssh_key_type { - - /** - * RSA key. - */ - SSH_KEY_RSA, - - /** - * DSA key. - */ - SSH_KEY_DSA - -} guac_common_ssh_key_type; +#define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB" /** * Abstraction of a key used for SSH authentication. */ typedef struct guac_common_ssh_key { - /** - * The type of this key. - */ - guac_common_ssh_key_type type; - - /** - * Underlying RSA private key, if any. - */ - RSA* rsa; - - /** - * Underlying DSA private key, if any. - */ - DSA* dsa; - - /** - * The associated public key, encoded as necessary for SSH. - */ - char* public_key; - - /** - * The length of the public key, in bytes. - */ - int public_key_length; - /** * The private key, encoded as necessary for SSH. */ @@ -104,6 +56,11 @@ typedef struct guac_common_ssh_key { */ int private_key_length; + /** + * The private key's passphrase, if any. + */ + char *passphrase; + } guac_common_ssh_key; /** @@ -144,31 +101,6 @@ const char* guac_common_ssh_key_error(); */ void guac_common_ssh_key_free(guac_common_ssh_key* key); -/** - * Signs the given data using the given key, returning the length of the - * signature in bytes, or a value less than zero on error. - * - * @param key - * The key to use when signing the given data. - * - * @param data - * The arbitrary data to sign. - * - * @param length - * The length of the arbitrary data being signed, in bytes. - * - * @param sig - * The buffer into which the signature should be written. The buffer must - * be at least DSA_SIG_SIZE for DSA keys. For RSA keys, the signature size - * is dependent only on key size, and is equal to the length of the - * modulus, in bytes. - * - * @return - * The number of bytes in the resulting signature. - */ -int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, - int length, unsigned char* sig); - /** * Verifies the host key for the given hostname/port combination against * one or more known_hosts entries. The known_host entries can either be a diff --git a/src/common-ssh/common-ssh/rsa-compat.h b/src/common-ssh/common-ssh/rsa-compat.h deleted file mode 100644 index 5c6763bb..00000000 --- a/src/common-ssh/common-ssh/rsa-compat.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef GUAC_COMMON_SSH_RSA_COMPAT_H -#define GUAC_COMMON_SSH_RSA_COMPAT_H - -#include "config.h" - -#include -#include - -#ifndef HAVE_RSA_GET0_KEY -/** - * RSA_get0_key() implementation for versions of OpenSSL which lack this - * function (pre 1.1). - * - * See: https://www.openssl.org/docs/man1.1.0/crypto/RSA_get0_key.html - */ -void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n, - const BIGNUM** e, const BIGNUM**d); -#endif - -#endif - diff --git a/src/common-ssh/dsa-compat.c b/src/common-ssh/dsa-compat.c deleted file mode 100644 index 82ec3d0e..00000000 --- a/src/common-ssh/dsa-compat.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include -#include - -#include - -#ifndef HAVE_DSA_GET0_PQG -void DSA_get0_pqg(const DSA* dsa_key, const BIGNUM** p, - const BIGNUM** q, const BIGNUM** g) { - - /* Retrieve all requested internal values */ - if (p != NULL) *p = dsa_key->p; - if (q != NULL) *q = dsa_key->q; - if (g != NULL) *g = dsa_key->g; - -} -#endif - -#ifndef HAVE_DSA_GET0_KEY -void DSA_get0_key(const DSA* dsa_key, const BIGNUM** pub_key, - const BIGNUM** priv_key) { - - /* Retrieve all requested internal values */ - if (pub_key != NULL) *pub_key = dsa_key->pub_key; - if (priv_key != NULL) *priv_key = dsa_key->priv_key; - -} -#endif - -#ifndef HAVE_DSA_SIG_GET0 -void DSA_SIG_get0(const DSA_SIG* dsa_sig, const BIGNUM** r, const BIGNUM** s) { - - /* Retrieve all requested internal values */ - if (r != NULL) *r = dsa_sig->r; - if (s != NULL) *s = dsa_sig->s; - -} -#endif - diff --git a/src/common-ssh/key.c b/src/common-ssh/key.c index f835e4cc..cecfb413 100644 --- a/src/common-ssh/key.c +++ b/src/common-ssh/key.c @@ -20,9 +20,9 @@ #include "config.h" #include "common-ssh/buffer.h" -#include "common-ssh/dsa-compat.h" #include "common-ssh/key.h" -#include "common-ssh/rsa-compat.h" + +#include #include #include @@ -33,119 +33,118 @@ #include #include +#include #include #include #include +/** + * Check for a PKCS#1/PKCS#8 ENCRYPTED marker. + * + * @param data + * The buffer to scan. + * @param length + * The length of the buffer. + * + * @return + * True if the buffer contains the marker, false otherwise. + */ +static bool is_pkcs_encrypted_key(char* data, int length) { + return guac_strnstr(data, "ENCRYPTED", length) != NULL; +} + +/** + * Check for a PEM header & initial base64-encoded data indicating this is an + * OpenSSH v1 key. + * + * @param data + * The buffer to scan. + * @param length + * The length of the buffer. + * + * @return + * True if the buffer contains a private key, false otherwise. + */ +static bool is_ssh_private_key(char* data, int length) { + if (length < sizeof(OPENSSH_V1_KEY_HEADER) - 1) { + return false; + } + return !strncmp(data, OPENSSH_V1_KEY_HEADER, sizeof(OPENSSH_V1_KEY_HEADER) - 1); +} + +/** + * Assuming an offset into a key past the header, check for the base64-encoded + * data indicating this key is not protected by a passphrase. + * + * @param data + * The buffer to scan. + * @param length + * The length of the buffer. + * + * @return + * True if the buffer contains an unencrypted key, false otherwise. + */ +static bool is_ssh_key_unencrypted(char* data, int length) { + if (length < sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1) { + return false; + } + return !strncmp(data, OPENSSH_V1_UNENCRYPTED_KEY, sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1); +} + +/** + * A passphrase is needed if the key is an encrypted PKCS#1/PKCS#8 key OR if + * the key is both an OpenSSH v1 key AND there isn't a marker indicating the + * key is unprotected. + * + * @param data + * The buffer to scan. + * @param length + * The length of the buffer. + * + * @return + * True if the buffer contains a key needing a passphrase, false otherwise. + */ +static bool is_passphrase_needed(char* data, int length) { + /* Is this an encrypted PKCS#1/PKCS#8 key? */ + if (is_pkcs_encrypted_key(data, length)) { + return true; + } + + /* Is this an OpenSSH v1 key? */ + if (is_ssh_private_key(data, length)) { + /* This is safe due to the check in is_ssh_private_key. */ + data += sizeof(OPENSSH_V1_KEY_HEADER) - 1; + length -= sizeof(OPENSSH_V1_KEY_HEADER) - 1; + /* If this is NOT unprotected, we need a passphrase. */ + if (!is_ssh_key_unencrypted(data, length)) { + return true; + } + } + + return false; +} + guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length, char* passphrase) { - guac_common_ssh_key* key; - BIO* key_bio; + /* Because libssh2 will do the actual key parsing (to let it deal with + * different key algorithms) we need to perform a heuristic here to check + * if a passphrase is needed. This could allow junk keys through that + * would never be able to auth. libssh2 should display errors to help + * admins track down malformed keys and delete or replace them. + */ - char* public_key; - char* pos; - - /* Create BIO for reading key from memory */ - key_bio = BIO_new_mem_buf(data, length); - - /* If RSA key, load RSA */ - if (length > sizeof(SSH_RSA_KEY_HEADER)-1 - && memcmp(SSH_RSA_KEY_HEADER, data, - sizeof(SSH_RSA_KEY_HEADER)-1) == 0) { - - RSA* rsa_key; - - const BIGNUM* key_e; - const BIGNUM* key_n; - - /* Read key */ - rsa_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, passphrase); - if (rsa_key == NULL) - return NULL; - - /* Allocate key */ - key = malloc(sizeof(guac_common_ssh_key)); - key->rsa = rsa_key; - - /* Set type */ - key->type = SSH_KEY_RSA; - - /* Allocate space for public key */ - public_key = malloc(4096); - pos = public_key; - - /* Retrieve public key */ - RSA_get0_key(rsa_key, &key_n, &key_e, NULL); - - /* Send public key formatted for SSH */ - guac_common_ssh_buffer_write_string(&pos, "ssh-rsa", sizeof("ssh-rsa")-1); - guac_common_ssh_buffer_write_bignum(&pos, key_e); - guac_common_ssh_buffer_write_bignum(&pos, key_n); - - /* Save public key to structure */ - key->public_key = public_key; - key->public_key_length = pos - public_key; - - } - - /* If DSA key, load DSA */ - else if (length > sizeof(SSH_DSA_KEY_HEADER)-1 - && memcmp(SSH_DSA_KEY_HEADER, data, - sizeof(SSH_DSA_KEY_HEADER)-1) == 0) { - - DSA* dsa_key; - - const BIGNUM* key_p; - const BIGNUM* key_q; - const BIGNUM* key_g; - const BIGNUM* pub_key; - - /* Read key */ - dsa_key = PEM_read_bio_DSAPrivateKey(key_bio, NULL, NULL, passphrase); - if (dsa_key == NULL) - return NULL; - - /* Allocate key */ - key = malloc(sizeof(guac_common_ssh_key)); - key->dsa = dsa_key; - - /* Set type */ - key->type = SSH_KEY_DSA; - - /* Allocate space for public key */ - public_key = malloc(4096); - pos = public_key; - - /* Retrieve public key */ - DSA_get0_pqg(dsa_key, &key_p, &key_q, &key_g); - DSA_get0_key(dsa_key, &pub_key, NULL); - - /* Send public key formatted for SSH */ - guac_common_ssh_buffer_write_string(&pos, "ssh-dss", sizeof("ssh-dss")-1); - guac_common_ssh_buffer_write_bignum(&pos, key_p); - guac_common_ssh_buffer_write_bignum(&pos, key_q); - guac_common_ssh_buffer_write_bignum(&pos, key_g); - guac_common_ssh_buffer_write_bignum(&pos, pub_key); - - /* Save public key to structure */ - key->public_key = public_key; - key->public_key_length = pos - public_key; - - } - - /* Otherwise, unsupported type */ - else { - BIO_free(key_bio); + if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0')) return NULL; - } + + guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key)); /* Copy private key to structure */ key->private_key_length = length; key->private_key = malloc(length); memcpy(key->private_key, data, length); + key->passphrase = strdup(passphrase); - BIO_free(key_bio); return key; } @@ -159,93 +158,11 @@ const char* guac_common_ssh_key_error() { void guac_common_ssh_key_free(guac_common_ssh_key* key) { - /* Free key-specific data */ - if (key->type == SSH_KEY_RSA) - RSA_free(key->rsa); - else if (key->type == SSH_KEY_DSA) - DSA_free(key->dsa); - free(key->private_key); - free(key->public_key); + free(key->passphrase); free(key); } -int guac_common_ssh_key_sign(guac_common_ssh_key* key, const char* data, - int length, unsigned char* sig) { - - const EVP_MD* md; - - unsigned char digest[EVP_MAX_MD_SIZE]; - unsigned int dlen, len; - - /* Get SHA1 digest */ - if ((md = EVP_get_digestbynid(NID_sha1)) == NULL) - return -1; - - /* Allocate digest context */ - EVP_MD_CTX* md_ctx = EVP_MD_CTX_create(); - if (md_ctx == NULL) - return -1; - - /* Digest data */ - EVP_DigestInit(md_ctx, md); - EVP_DigestUpdate(md_ctx, data, length); - EVP_DigestFinal(md_ctx, digest, &dlen); - - /* Digest context no longer needed */ - EVP_MD_CTX_destroy(md_ctx); - - /* Sign with key */ - switch (key->type) { - - case SSH_KEY_RSA: - if (RSA_sign(NID_sha1, digest, dlen, sig, &len, key->rsa) == 1) - return len; - break; - - case SSH_KEY_DSA: { - - DSA_SIG* dsa_sig = DSA_do_sign(digest, dlen, key->dsa); - if (dsa_sig != NULL) { - - const BIGNUM* sig_r; - const BIGNUM* sig_s; - - /* Retrieve DSA signature values */ - DSA_SIG_get0(dsa_sig, &sig_r, &sig_s); - - /* Compute size of each half of signature */ - int rlen = BN_num_bytes(sig_r); - int slen = BN_num_bytes(sig_s); - - /* Ensure each number is within the required size */ - if (rlen > DSA_SIG_NUMBER_SIZE || slen > DSA_SIG_NUMBER_SIZE) - return -1; - - /* Init to all zeroes */ - memset(sig, 0, DSA_SIG_SIZE); - - /* Add R at the end of the first block of the signature */ - BN_bn2bin(sig_r, sig + DSA_SIG_SIZE - - DSA_SIG_NUMBER_SIZE - rlen); - - /* Add S at the end of the second block of the signature */ - BN_bn2bin(sig_s, sig + DSA_SIG_SIZE - slen); - - /* Done */ - DSA_SIG_free(dsa_sig); - return DSA_SIG_SIZE; - - } - - } - - } - - return -1; - -} - int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client, const char* host_key, const char* hostname, int port, const char* remote_hostkey, const size_t remote_hostkey_len) { diff --git a/src/common-ssh/rsa-compat.c b/src/common-ssh/rsa-compat.c deleted file mode 100644 index 915536a8..00000000 --- a/src/common-ssh/rsa-compat.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "config.h" - -#include -#include - -#include - -#ifndef HAVE_RSA_GET0_KEY -void RSA_get0_key(const RSA* rsa_key, const BIGNUM** n, - const BIGNUM** e, const BIGNUM**d) { - - /* Retrieve all requested internal values */ - if (n != NULL) *n = rsa_key->n; - if (e != NULL) *e = rsa_key->e; - if (d != NULL) *d = rsa_key->d; - -} -#endif - diff --git a/src/common-ssh/ssh.c b/src/common-ssh/ssh.c index b45a1f5e..a847e7c3 100644 --- a/src/common-ssh/ssh.c +++ b/src/common-ssh/ssh.c @@ -183,55 +183,6 @@ void guac_common_ssh_uninit() { #endif } -/** - * Callback invoked by libssh2 when libssh2_userauth_publickkey() is invoked. - * This callback must sign the given data, returning the signature as newly- - * allocated buffer space. - * - * @param session - * The SSH session for which the signature is being generated. - * - * @param sig - * A pointer to the buffer space containing the signature. This callback - * MUST allocate and assign this space. - * - * @param sig_len - * The length of the signature within the allocated buffer space, in bytes. - * This value must be set to the size of the signature after the signing - * operation completes. - * - * @param data - * The arbitrary data that must be signed. - * - * @param data_len - * The length of the arbitrary data to be signed, in bytes. - * - * @param abstract - * The value of the abstract parameter provided with the corresponding call - * to libssh2_userauth_publickey(). - * - * @return - * Zero on success, non-zero if the signing operation failed. - */ -static int guac_common_ssh_sign_callback(LIBSSH2_SESSION* session, - unsigned char** sig, size_t* sig_len, - const unsigned char* data, size_t data_len, void **abstract) { - - guac_common_ssh_key* key = (guac_common_ssh_key*) abstract; - int length; - - /* Allocate space for signature */ - *sig = malloc(4096); - - /* Sign with key */ - length = guac_common_ssh_key_sign(key, (const char*) data, data_len, *sig); - if (length < 0) - return 1; - - *sig_len = length; - return 0; -} - /** * Callback for the keyboard-interactive authentication method. Currently * supports just one prompt for the password. This callback is invoked as @@ -324,8 +275,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } /* Get list of supported authentication methods */ + size_t username_len = strlen(user->username); char* user_authlist = libssh2_userauth_list(session, user->username, - strlen(user->username)); + username_len); /* If auth list is NULL, then authentication has succeeded with NONE */ if (user_authlist == NULL) { @@ -349,9 +301,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session) } /* Attempt public key auth */ - if (libssh2_userauth_publickey(session, user->username, - (unsigned char*) key->public_key, key->public_key_length, - guac_common_ssh_sign_callback, (void**) key)) { + if (libssh2_userauth_publickey_frommemory(session, user->username, + username_len, NULL, 0, key->private_key, + key->private_key_length, key->passphrase)) { /* Abort on failure */ char* error_message; diff --git a/src/libguac/guacamole/string.h b/src/libguac/guacamole/string.h index a648c0c2..f720ba19 100644 --- a/src/libguac/guacamole/string.h +++ b/src/libguac/guacamole/string.h @@ -109,6 +109,28 @@ size_t guac_strlcpy(char* restrict dest, const char* restrict src, size_t n); */ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n); +/** + * Search for the null-terminated string needle in the possibly null- + * terminated haystack, looking at no more than len bytes. + * + * @param haystack + * The string to search. It may or may not be null-terminated. Only the + * first len bytes are searched. + * + * @param needle + * The string to look for. It must be null-terminated. + * + * @param len + * The maximum number of bytes to examine in haystack. + * + * @return + * A pointer to the first instance of needle within haystack, or NULL if + * needle does not exist in haystack. If needle is the empty string, + * haystack is returned. + * + */ +char* guac_strnstr(const char *haystack, const char *needle, size_t len); + /** * Simple wrapper for strdup() which behaves identically to standard strdup(), * except that NULL will be returned if the provided string is NULL. diff --git a/src/libguac/string.c b/src/libguac/string.c index e75f7c18..260465da 100644 --- a/src/libguac/string.c +++ b/src/libguac/string.c @@ -81,6 +81,38 @@ size_t guac_strlcat(char* restrict dest, const char* restrict src, size_t n) { } +char* guac_strnstr(const char *haystack, const char *needle, size_t len) { + +#ifdef HAVE_STRNSTR + return strnstr(haystack, needle, len); +#else + char* chr; + size_t nlen = strlen(needle), off = 0; + + /* Follow documented API: return haystack if needle is the empty string. */ + if (nlen == 0) + return (char *)haystack; + + /* Use memchr to find candidates. It might be optimized in asm. */ + while (off < len && NULL != (chr = memchr(haystack + off, needle[0], len - off))) { + /* chr is guaranteed to be in bounds of and >= haystack. */ + off = chr - haystack; + /* If needle would go beyond provided len, it doesn't exist in haystack. */ + if (off + nlen > len) + return NULL; + /* Now that we know we have at least nlen bytes, compare them. */ + if (!memcmp(chr, needle, nlen)) + return chr; + /* Make sure we make progress. */ + off += 1; + } + + /* memchr ran out of candidates, needle wasn't found. */ + return NULL; +#endif + +} + char* guac_strdup(const char* str) { /* Return NULL if no string provided */ diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index d406c4cd..b7452cf0 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -48,6 +48,7 @@ test_libguac_SOURCES = \ string/strlcat.c \ string/strlcpy.c \ string/strljoin.c \ + string/strnstr.c \ unicode/charsize.c \ unicode/read.c \ unicode/strlen.c \ diff --git a/src/libguac/tests/string/strnstr.c b/src/libguac/tests/string/strnstr.c new file mode 100644 index 00000000..ffd02dbc --- /dev/null +++ b/src/libguac/tests/string/strnstr.c @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include + +#include +#include + +/** + * Verify guac_strnstr() behaviors: + */ +void test_string__strnstr() { + char haystack[8] = {'a', 'h', 'i', ' ', 't', 'u', 'n', 'a'}; + char* result; + + /* needle exists at start of haystack */ + result = guac_strnstr(haystack, "ah", sizeof(haystack)); + CU_ASSERT_EQUAL(result, haystack); + + /* needle exists in the middle of haystack */ + result = guac_strnstr(haystack, "hi", sizeof(haystack)); + CU_ASSERT_EQUAL(result, haystack + 1); + + /* needle exists at end of haystack */ + result = guac_strnstr(haystack, "tuna", sizeof(haystack)); + CU_ASSERT_EQUAL(result, haystack + 4); + + /* needle doesn't exist in haystack, needle[0] isn't in haystack */ + result = guac_strnstr(haystack, "mahi", sizeof(haystack)); + CU_ASSERT_EQUAL(result, NULL); + + /* + * needle doesn't exist in haystack, needle[0] is in haystack, + * length wouldn't allow needle to exist + */ + result = guac_strnstr(haystack, "narwhal", sizeof(haystack)); + CU_ASSERT_EQUAL(result, NULL); + + /* + * needle doesn't exist in haystack, needle[0] is in haystack, + * length would allow needle to exist + */ + result = guac_strnstr(haystack, "taco", sizeof(haystack)); + CU_ASSERT_EQUAL(result, NULL); + + /* + * needle doesn't exist in haystack, needle[0] is in haystack + * multiple times + */ + result = guac_strnstr(haystack, "ahha", sizeof(haystack)); + CU_ASSERT_EQUAL(result, NULL); + + /* empty needle should return haystack according to API docs */ + result = guac_strnstr(haystack, "", sizeof(haystack)); + CU_ASSERT_EQUAL(result, haystack); +}