/* * 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 #include #include #include #include #include #include #include #include #include #include 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* fingerprint, const size_t fp_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) { guac_client_log(client, GUAC_LOG_ERROR, "Failure trying to load SSH host keys."); 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 fingerprint against known hosts */ int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port, fingerprint, fp_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; }