332 lines
9.8 KiB
C
332 lines
9.8 KiB
C
/*
|
|
* 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 "common-ssh/buffer.h"
|
|
#include "common-ssh/dsa-compat.h"
|
|
#include "common-ssh/key.h"
|
|
#include "common-ssh/rsa-compat.h"
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/bn.h>
|
|
#include <openssl/dsa.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/obj_mac.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/rsa.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
|
|
char* passphrase) {
|
|
|
|
guac_common_ssh_key* key;
|
|
BIO* key_bio;
|
|
|
|
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);
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy private key to structure */
|
|
key->private_key_length = length;
|
|
key->private_key = malloc(length);
|
|
memcpy(key->private_key, data, length);
|
|
|
|
BIO_free(key_bio);
|
|
return key;
|
|
|
|
}
|
|
|
|
const char* guac_common_ssh_key_error() {
|
|
|
|
/* Return static error string */
|
|
return ERR_reason_error_string(ERR_get_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);
|
|
}
|
|
|
|
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) {
|
|
|
|
LIBSSH2_KNOWNHOSTS* ssh_known_hosts = libssh2_knownhost_init(session);
|
|
int known_hosts = 0;
|
|
|
|
/* Add host key provided from settings */
|
|
if (host_key && strcmp(host_key, "") != 0) {
|
|
|
|
known_hosts = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key),
|
|
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
|
|
|
|
/* readline function returns 0 on success, so we increment to indicate a valid entry */
|
|
if (known_hosts == 0)
|
|
known_hosts++;
|
|
|
|
}
|
|
|
|
/* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */
|
|
else {
|
|
|
|
const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts";
|
|
if (access(guac_known_hosts, F_OK) != -1)
|
|
known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
|
|
|
|
}
|
|
|
|
/* If there's an error provided, abort connection and return that. */
|
|
if (known_hosts < 0) {
|
|
|
|
char* errmsg;
|
|
int errval = libssh2_session_last_error(session, &errmsg, NULL, 0);
|
|
guac_client_log(client, GUAC_LOG_ERROR,
|
|
"Error %d trying to load SSH host keys: %s", errval, errmsg);
|
|
|
|
libssh2_knownhost_free(ssh_known_hosts);
|
|
return known_hosts;
|
|
|
|
}
|
|
|
|
/* No host keys were loaded, so we bail out checking and continue the connection. */
|
|
else if (known_hosts == 0) {
|
|
guac_client_log(client, GUAC_LOG_WARNING,
|
|
"No known host keys provided, host identity will not be verified.");
|
|
libssh2_knownhost_free(ssh_known_hosts);
|
|
return known_hosts;
|
|
}
|
|
|
|
|
|
/* Check remote host key against known hosts */
|
|
int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port,
|
|
remote_hostkey, remote_hostkey_len,
|
|
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
|
|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
|
|
NULL);
|
|
|
|
/* Deal with the return of the host key check */
|
|
switch (kh_check) {
|
|
case LIBSSH2_KNOWNHOST_CHECK_MATCH:
|
|
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
"Host key match found for %s", hostname);
|
|
break;
|
|
case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
|
|
guac_client_log(client, GUAC_LOG_ERROR,
|
|
"Host key not found for %s.", hostname);
|
|
break;
|
|
case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
|
|
guac_client_log(client, GUAC_LOG_ERROR,
|
|
"Host key does not match known hosts entry for %s", hostname);
|
|
break;
|
|
case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
|
|
default:
|
|
guac_client_log(client, GUAC_LOG_ERROR,
|
|
"Host %s could not be checked against known hosts.",
|
|
hostname);
|
|
}
|
|
|
|
/* Return the check value */
|
|
libssh2_knownhost_free(ssh_known_hosts);
|
|
return kh_check;
|
|
|
|
}
|