cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

key.c (8035B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "config.h"
     21
     22#include "common-ssh/buffer.h"
     23#include "common-ssh/key.h"
     24
     25#include <guacamole/mem.h>
     26#include <guacamole/string.h>
     27
     28#include <openssl/bio.h>
     29#include <openssl/bn.h>
     30#include <openssl/dsa.h>
     31#include <openssl/err.h>
     32#include <openssl/evp.h>
     33#include <openssl/obj_mac.h>
     34#include <openssl/pem.h>
     35#include <openssl/rsa.h>
     36
     37#include <stdbool.h>
     38#include <stdlib.h>
     39#include <string.h>
     40#include <unistd.h>
     41
     42/**
     43 * Check for a PKCS#1/PKCS#8 ENCRYPTED marker.
     44 *
     45 * @param data
     46 *     The buffer to scan.
     47 * @param length
     48 *     The length of the buffer.
     49 *
     50 * @return
     51 *     True if the buffer contains the marker, false otherwise.
     52 */
     53static bool is_pkcs_encrypted_key(char* data, int length) {
     54    return guac_strnstr(data, "ENCRYPTED", length) != NULL;
     55}
     56
     57/**
     58 * Check for a PEM header & initial base64-encoded data indicating this is an
     59 * OpenSSH v1 key.
     60 *
     61 * @param data
     62 *     The buffer to scan.
     63 * @param length
     64 *     The length of the buffer.
     65 *
     66 * @return
     67 *     True if the buffer contains a private key, false otherwise.
     68 */
     69static bool is_ssh_private_key(char* data, int length) {
     70    if (length < sizeof(OPENSSH_V1_KEY_HEADER) - 1) {
     71        return false;
     72    }
     73    return !strncmp(data, OPENSSH_V1_KEY_HEADER, sizeof(OPENSSH_V1_KEY_HEADER) - 1);
     74}
     75
     76/**
     77 * Assuming an offset into a key past the header, check for the base64-encoded
     78 * data indicating this key is not protected by a passphrase.
     79 *
     80 * @param data
     81 *     The buffer to scan.
     82 * @param length
     83 *     The length of the buffer.
     84 *
     85 * @return
     86 *     True if the buffer contains an unencrypted key, false otherwise.
     87 */
     88static bool is_ssh_key_unencrypted(char* data, int length) {
     89    if (length < sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1) {
     90        return false;
     91    }
     92    return !strncmp(data, OPENSSH_V1_UNENCRYPTED_KEY, sizeof(OPENSSH_V1_UNENCRYPTED_KEY) - 1);
     93}
     94
     95/**
     96 * A passphrase is needed if the key is an encrypted PKCS#1/PKCS#8 key OR if
     97 * the key is both an OpenSSH v1 key AND there isn't a marker indicating the
     98 * key is unprotected.
     99 *
    100 * @param data
    101 *     The buffer to scan.
    102 * @param length
    103 *     The length of the buffer.
    104 *
    105 * @return
    106 *     True if the buffer contains a key needing a passphrase, false otherwise.
    107 */
    108static bool is_passphrase_needed(char* data, int length) {
    109    /* Is this an encrypted PKCS#1/PKCS#8 key? */
    110    if (is_pkcs_encrypted_key(data, length)) {
    111        return true;
    112    }
    113
    114    /* Is this an OpenSSH v1 key? */
    115    if (is_ssh_private_key(data, length)) {
    116        /* This is safe due to the check in is_ssh_private_key. */
    117        data += sizeof(OPENSSH_V1_KEY_HEADER) - 1;
    118        length -= sizeof(OPENSSH_V1_KEY_HEADER) - 1;
    119        /* If this is NOT unprotected, we need a passphrase. */
    120        if (!is_ssh_key_unencrypted(data, length)) {
    121            return true;
    122        }
    123    }
    124
    125    return false;
    126}
    127
    128guac_common_ssh_key* guac_common_ssh_key_alloc(char* data, int length,
    129        char* passphrase) {
    130
    131    /* Because libssh2 will do the actual key parsing (to let it deal with
    132     * different key algorithms) we need to perform a heuristic here to check
    133     * if a passphrase is needed. This could allow junk keys through that
    134     * would never be able to auth. libssh2 should display errors to help
    135     * admins track down malformed keys and delete or replace them.
    136     */
    137
    138    if (is_passphrase_needed(data, length) && (passphrase == NULL || *passphrase == '\0'))
    139        return NULL;
    140
    141    guac_common_ssh_key* key = guac_mem_alloc(sizeof(guac_common_ssh_key));
    142
    143    /* Copy private key to structure */
    144    key->private_key_length = length;
    145    key->private_key = guac_mem_alloc(length);
    146    memcpy(key->private_key, data, length);
    147    key->passphrase = guac_strdup(passphrase);
    148
    149    return key;
    150
    151}
    152
    153const char* guac_common_ssh_key_error() {
    154
    155    /* Return static error string */
    156    return ERR_reason_error_string(ERR_get_error());
    157
    158}
    159
    160void guac_common_ssh_key_free(guac_common_ssh_key* key) {
    161    guac_mem_free(key->private_key);
    162    guac_mem_free(key->passphrase);
    163    guac_mem_free(key);
    164}
    165
    166int guac_common_ssh_verify_host_key(LIBSSH2_SESSION* session, guac_client* client,
    167        const char* host_key, const char* hostname, int port, const char* remote_hostkey,
    168        const size_t remote_hostkey_len) {
    169
    170    LIBSSH2_KNOWNHOSTS* ssh_known_hosts = libssh2_knownhost_init(session);
    171    int known_hosts = 0;
    172
    173    /* Add host key provided from settings */
    174    if (host_key && strcmp(host_key, "") != 0) {
    175
    176        known_hosts = libssh2_knownhost_readline(ssh_known_hosts, host_key, strlen(host_key),
    177                LIBSSH2_KNOWNHOST_FILE_OPENSSH);
    178
    179        /* readline function returns 0 on success, so we increment to indicate a valid entry */
    180        if (known_hosts == 0)
    181            known_hosts++;
    182
    183    }
    184
    185    /* Otherwise, we look for a ssh_known_hosts file within GUACAMOLE_HOME and read that in. */
    186    else {
    187
    188        const char *guac_known_hosts = "/etc/guacamole/ssh_known_hosts";
    189        if (access(guac_known_hosts, F_OK) != -1)
    190            known_hosts = libssh2_knownhost_readfile(ssh_known_hosts, guac_known_hosts, LIBSSH2_KNOWNHOST_FILE_OPENSSH);
    191
    192    }
    193
    194    /* If there's an error provided, abort connection and return that. */
    195    if (known_hosts < 0) {
    196
    197        char* errmsg;
    198        int errval = libssh2_session_last_error(session, &errmsg, NULL, 0);
    199        guac_client_log(client, GUAC_LOG_ERROR,
    200            "Error %d trying to load SSH host keys: %s", errval, errmsg);
    201
    202        libssh2_knownhost_free(ssh_known_hosts);
    203        return known_hosts;
    204
    205    }
    206
    207    /* No host keys were loaded, so we bail out checking and continue the connection. */
    208    else if (known_hosts == 0) {
    209        guac_client_log(client, GUAC_LOG_WARNING,
    210            "No known host keys provided, host identity will not be verified.");
    211        libssh2_knownhost_free(ssh_known_hosts);
    212        return known_hosts;
    213    }
    214
    215
    216    /* Check remote host key against known hosts */
    217    int kh_check = libssh2_knownhost_checkp(ssh_known_hosts, hostname, port,
    218                                            remote_hostkey, remote_hostkey_len,
    219                                            LIBSSH2_KNOWNHOST_TYPE_PLAIN|
    220                                            LIBSSH2_KNOWNHOST_KEYENC_RAW,
    221                                            NULL);
    222
    223    /* Deal with the return of the host key check */
    224    switch (kh_check) {
    225        case LIBSSH2_KNOWNHOST_CHECK_MATCH:
    226            guac_client_log(client, GUAC_LOG_DEBUG,
    227                "Host key match found for %s", hostname);
    228            break;
    229        case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND:
    230            guac_client_log(client, GUAC_LOG_ERROR,
    231                "Host key not found for %s.", hostname);
    232            break;
    233        case LIBSSH2_KNOWNHOST_CHECK_MISMATCH:
    234            guac_client_log(client, GUAC_LOG_ERROR,
    235                "Host key does not match known hosts entry for %s", hostname);
    236            break;
    237        case LIBSSH2_KNOWNHOST_CHECK_FAILURE:
    238        default:
    239            guac_client_log(client, GUAC_LOG_ERROR,
    240                "Host %s could not be checked against known hosts.",
    241                hostname);
    242    }
    243
    244    /* Return the check value */
    245    libssh2_knownhost_free(ssh_known_hosts);
    246    return kh_check;
    247
    248}