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:
parent
491be8382a
commit
f84db7d166
31
configure.ac
31
configure.ac
@ -160,6 +160,11 @@ AC_CHECK_DECL([strlcat],
|
|||||||
[Whether strlcat() is defined])],,
|
[Whether strlcat() is defined])],,
|
||||||
[#include <string.h>])
|
[#include <string.h>])
|
||||||
|
|
||||||
|
AC_CHECK_DECL([strnstr],
|
||||||
|
[AC_DEFINE([HAVE_STRNSTR],,
|
||||||
|
[Whether strnstr() is defined])],,
|
||||||
|
[#include <string.h>])
|
||||||
|
|
||||||
# Typedefs
|
# Typedefs
|
||||||
AC_TYPE_SIZE_T
|
AC_TYPE_SIZE_T
|
||||||
AC_TYPE_SSIZE_T
|
AC_TYPE_SSIZE_T
|
||||||
@ -320,30 +325,6 @@ then
|
|||||||
else
|
else
|
||||||
AC_DEFINE([ENABLE_SSL],, [Whether SSL-related support is enabled])
|
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
|
# OpenSSL 1.1 does away with explicit threading callbacks
|
||||||
AC_MSG_CHECKING([whether libssl requires threading callbacks])
|
AC_MSG_CHECKING([whether libssl requires threading callbacks])
|
||||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
|
||||||
@ -932,7 +913,7 @@ if test "x$with_ssh" != "xno"
|
|||||||
then
|
then
|
||||||
have_libssh2=yes
|
have_libssh2=yes
|
||||||
|
|
||||||
AC_CHECK_LIB([ssh2], [libssh2_session_init_ex],
|
AC_CHECK_LIB([ssh2], [libssh2_userauth_publickey_frommemory],
|
||||||
[SSH_LIBS="$SSH_LIBS -lssh2"],
|
[SSH_LIBS="$SSH_LIBS -lssh2"],
|
||||||
[have_libssh2=no])
|
[have_libssh2=no])
|
||||||
fi
|
fi
|
||||||
|
@ -31,8 +31,6 @@ SUBDIRS = . tests
|
|||||||
|
|
||||||
libguac_common_ssh_la_SOURCES = \
|
libguac_common_ssh_la_SOURCES = \
|
||||||
buffer.c \
|
buffer.c \
|
||||||
dsa-compat.c \
|
|
||||||
rsa-compat.c \
|
|
||||||
sftp.c \
|
sftp.c \
|
||||||
ssh.c \
|
ssh.c \
|
||||||
key.c \
|
key.c \
|
||||||
@ -40,8 +38,6 @@ libguac_common_ssh_la_SOURCES = \
|
|||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
common-ssh/buffer.h \
|
common-ssh/buffer.h \
|
||||||
common-ssh/dsa-compat.h \
|
|
||||||
common-ssh/rsa-compat.h \
|
|
||||||
common-ssh/key.h \
|
common-ssh/key.h \
|
||||||
common-ssh/sftp.h \
|
common-ssh/sftp.h \
|
||||||
common-ssh/ssh.h \
|
common-ssh/ssh.h \
|
||||||
|
@ -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
|
|
||||||
|
|
@ -25,75 +25,27 @@
|
|||||||
#include <guacamole/client.h>
|
#include <guacamole/client.h>
|
||||||
#include <libssh2.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-----"
|
#define OPENSSH_V1_UNENCRYPTED_KEY "AAAABG5vbmUAAAAEbm9uZQAAAAAAAAAB"
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction of a key used for SSH authentication.
|
* Abstraction of a key used for SSH authentication.
|
||||||
*/
|
*/
|
||||||
typedef struct guac_common_ssh_key {
|
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.
|
* The private key, encoded as necessary for SSH.
|
||||||
*/
|
*/
|
||||||
@ -104,6 +56,11 @@ typedef struct guac_common_ssh_key {
|
|||||||
*/
|
*/
|
||||||
int private_key_length;
|
int private_key_length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The private key's passphrase, if any.
|
||||||
|
*/
|
||||||
|
char *passphrase;
|
||||||
|
|
||||||
} guac_common_ssh_key;
|
} 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);
|
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
|
* 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
|
* one or more known_hosts entries. The known_host entries can either be a
|
||||||
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -20,9 +20,9 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include "common-ssh/buffer.h"
|
#include "common-ssh/buffer.h"
|
||||||
#include "common-ssh/dsa-compat.h"
|
|
||||||
#include "common-ssh/key.h"
|
#include "common-ssh/key.h"
|
||||||
#include "common-ssh/rsa-compat.h"
|
|
||||||
|
#include <guacamole/string.h>
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/bn.h>
|
#include <openssl/bn.h>
|
||||||
@ -33,119 +33,118 @@
|
|||||||
#include <openssl/pem.h>
|
#include <openssl/pem.h>
|
||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.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,
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
||||||
char* passphrase) {
|
char* passphrase) {
|
||||||
|
|
||||||
guac_common_ssh_key* key;
|
/* Because libssh2 will do the actual key parsing (to let it deal with
|
||||||
BIO* key_bio;
|
* 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;
|
if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
|
||||||
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);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
guac_common_ssh_key* key = malloc(sizeof(guac_common_ssh_key));
|
||||||
|
|
||||||
/* Copy private key to structure */
|
/* Copy private key to structure */
|
||||||
key->private_key_length = length;
|
key->private_key_length = length;
|
||||||
key->private_key = malloc(length);
|
key->private_key = malloc(length);
|
||||||
memcpy(key->private_key, data, length);
|
memcpy(key->private_key, data, length);
|
||||||
|
key->passphrase = strdup(passphrase);
|
||||||
|
|
||||||
BIO_free(key_bio);
|
|
||||||
return key;
|
return key;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -159,93 +158,11 @@ const char* guac_common_ssh_key_error() {
|
|||||||
|
|
||||||
void guac_common_ssh_key_free(guac_common_ssh_key* key) {
|
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->private_key);
|
||||||
free(key->public_key);
|
free(key->passphrase);
|
||||||
free(key);
|
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,
|
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 char* host_key, const char* hostname, int port, const char* remote_hostkey,
|
||||||
const size_t remote_hostkey_len) {
|
const size_t remote_hostkey_len) {
|
||||||
|
@ -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
|
|
||||||
|
|
@ -183,55 +183,6 @@ void guac_common_ssh_uninit() {
|
|||||||
#endif
|
#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
|
* Callback for the keyboard-interactive authentication method. Currently
|
||||||
* supports just one prompt for the password. This callback is invoked as
|
* 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 */
|
/* Get list of supported authentication methods */
|
||||||
|
size_t username_len = strlen(user->username);
|
||||||
char* user_authlist = libssh2_userauth_list(session, 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 auth list is NULL, then authentication has succeeded with NONE */
|
||||||
if (user_authlist == NULL) {
|
if (user_authlist == NULL) {
|
||||||
@ -349,9 +301,9 @@ static int guac_common_ssh_authenticate(guac_common_ssh_session* common_session)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Attempt public key auth */
|
/* Attempt public key auth */
|
||||||
if (libssh2_userauth_publickey(session, user->username,
|
if (libssh2_userauth_publickey_frommemory(session, user->username,
|
||||||
(unsigned char*) key->public_key, key->public_key_length,
|
username_len, NULL, 0, key->private_key,
|
||||||
guac_common_ssh_sign_callback, (void**) key)) {
|
key->private_key_length, key->passphrase)) {
|
||||||
|
|
||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
char* error_message;
|
char* error_message;
|
||||||
|
@ -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);
|
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(),
|
* Simple wrapper for strdup() which behaves identically to standard strdup(),
|
||||||
* except that NULL will be returned if the provided string is NULL.
|
* except that NULL will be returned if the provided string is NULL.
|
||||||
|
@ -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) {
|
char* guac_strdup(const char* str) {
|
||||||
|
|
||||||
/* Return NULL if no string provided */
|
/* Return NULL if no string provided */
|
||||||
|
@ -48,6 +48,7 @@ test_libguac_SOURCES = \
|
|||||||
string/strlcat.c \
|
string/strlcat.c \
|
||||||
string/strlcpy.c \
|
string/strlcpy.c \
|
||||||
string/strljoin.c \
|
string/strljoin.c \
|
||||||
|
string/strnstr.c \
|
||||||
unicode/charsize.c \
|
unicode/charsize.c \
|
||||||
unicode/read.c \
|
unicode/read.c \
|
||||||
unicode/strlen.c \
|
unicode/strlen.c \
|
||||||
|
73
src/libguac/tests/string/strnstr.c
Normal file
73
src/libguac/tests/string/strnstr.c
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user