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.
This commit is contained in:
Joshua Roys 2021-12-15 09:35:46 -05:00
parent 491be8382a
commit f84db7d166
13 changed files with 256 additions and 548 deletions

View File

@ -160,6 +160,11 @@ AC_CHECK_DECL([strlcat],
[Whether strlcat() is defined])],,
[#include <string.h>])
AC_CHECK_DECL([strnstr],
[AC_DEFINE([HAVE_STRNSTR],,
[Whether strnstr() is defined])],,
[#include <string.h>])
# 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/dsa.h>])
# 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/dsa.h>])
# 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/dsa.h>])
# 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/rsa.h>])
# 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

View File

@ -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 \

View File

@ -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 <openssl/bn.h>
#include <openssl/dsa.h>
#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

View File

@ -25,75 +25,27 @@
#include <guacamole/client.h>
#include <libssh2.h>
#include <openssl/ossl_typ.h>
/**
* 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

View File

@ -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 <openssl/bn.h>
#include <openssl/rsa.h>
#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

View File

@ -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 <openssl/bn.h>
#include <openssl/dsa.h>
#include <stdlib.h>
#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

View File

@ -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 <guacamole/string.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
@ -33,119 +33,118 @@
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/**
* 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)
if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
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);
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) {

View File

@ -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 <openssl/bn.h>
#include <openssl/rsa.h>
#include <stdlib.h>
#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

View File

@ -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;

View File

@ -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.

View File

@ -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 */

View File

@ -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 \

View File

@ -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 <CUnit/CUnit.h>
#include <guacamole/string.h>
#include <stdlib.h>
#include <string.h>
/**
* 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);
}