cscg24-guacamole

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

ssl.c (7518B)


      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 "kubernetes.h"
     21#include "settings.h"
     22
     23#include <guacamole/client.h>
     24#include <openssl/asn1.h>
     25#include <openssl/bio.h>
     26#include <openssl/pem.h>
     27#include <openssl/ssl.h>
     28#include <openssl/x509v3.h>
     29#include <openssl/x509_vfy.h>
     30
     31/**
     32 * Tests whether the given hostname is, in fact, an IP address.
     33 *
     34 * @param hostname
     35 *     The hostname to test.
     36 *
     37 * @return
     38 *     Non-zero if the given hostname is an IP address, zero otherwise.
     39 */
     40static int guac_kubernetes_is_address(const char* hostname) {
     41
     42    /* Attempt to interpret the hostname as an IP address */
     43    ASN1_OCTET_STRING* ip = a2i_IPADDRESS(hostname);
     44
     45    /* If unsuccessful, the hostname is not an IP address */
     46    if (ip == NULL)
     47        return 0;
     48
     49    /* Converted hostname must be freed */
     50    ASN1_OCTET_STRING_free(ip);
     51    return 1;
     52
     53}
     54
     55/**
     56 * Parses the given PEM certificate, returning a new OpenSSL X509 structure
     57 * representing that certificate.
     58 *
     59 * @param pem
     60 *     The PEM certificate.
     61 *
     62 * @return
     63 *     An X509 structure representing the given certificate, or NULL if the
     64 *     certificate was unreadable.
     65 */
     66static X509* guac_kubernetes_read_cert(char* pem) {
     67
     68    /* Prepare a BIO which provides access to the in-memory CA cert */
     69    BIO* bio = BIO_new_mem_buf(pem, -1);
     70    if (bio == NULL)
     71        return NULL;
     72
     73    /* Read the CA cert as PEM */
     74    X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
     75    if (certificate == NULL) {
     76        BIO_free(bio);
     77        return NULL;
     78    }
     79
     80    return certificate;
     81
     82}
     83
     84/**
     85 * Parses the given PEM private key, returning a new OpenSSL EVP_PKEY structure
     86 * representing that key.
     87 *
     88 * @param pem
     89 *     The PEM private key.
     90 *
     91 * @return
     92 *     An EVP_KEY representing the given private key, or NULL if the private
     93 *     key was unreadable.
     94 */
     95static EVP_PKEY* guac_kubernetes_read_key(char* pem) {
     96
     97    /* Prepare a BIO which provides access to the in-memory key */
     98    BIO* bio = BIO_new_mem_buf(pem, -1);
     99    if (bio == NULL)
    100        return NULL;
    101
    102    /* Read the private key as PEM */
    103    EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
    104    if (key == NULL) {
    105        BIO_free(bio);
    106        return NULL;
    107    }
    108
    109    return key;
    110
    111}
    112
    113/**
    114 * OpenSSL certificate verification callback which universally accepts all
    115 * certificates without performing any verification at all.
    116 *
    117 * @param x509_ctx
    118 *     The current context of the certificate verification process. This
    119 *     parameter is ignored by this particular implementation of the callback.
    120 *
    121 * @param arg
    122 *     The arbitrary value passed to SSL_CTX_set_cert_verify_callback(). This
    123 *     parameter is ignored by this particular implementation of the callback.
    124 *
    125 * @return
    126 *     Strictly 0 if certificate verification fails, 1 if the certificate is
    127 *     verified. No other values are legal return values for this callback as
    128 *     documented by OpenSSL.
    129 */
    130static int guac_kubernetes_assume_cert_ok(X509_STORE_CTX* x509_ctx, void* arg) {
    131    return 1;
    132}
    133
    134void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) {
    135
    136    guac_kubernetes_client* kubernetes_client =
    137        (guac_kubernetes_client*) client->data;
    138
    139    guac_kubernetes_settings* settings = kubernetes_client->settings;
    140
    141    /* Bypass certificate checks if requested */
    142    if (settings->ignore_cert) {
    143        SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL);
    144        SSL_CTX_set_cert_verify_callback(context,
    145                guac_kubernetes_assume_cert_ok, NULL);
    146    }
    147
    148    /* Otherwise use the given CA certificate to validate (if any) */
    149    else if (settings->ca_cert != NULL) {
    150
    151        /* Read CA certificate from configuration data */
    152        X509* ca_cert = guac_kubernetes_read_cert(settings->ca_cert);
    153        if (ca_cert == NULL) {
    154            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    155                    "Provided CA certificate is unreadable");
    156            return;
    157        }
    158
    159        /* Add certificate to CA store */
    160        X509_STORE* ca_store = SSL_CTX_get_cert_store(context);
    161        if (!X509_STORE_add_cert(ca_store, ca_cert)) {
    162            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    163                    "Unable to add CA certificate to certificate store of "
    164                    "SSL context");
    165            return;
    166        }
    167
    168    }
    169
    170    /* Certificate for SSL/TLS client auth */
    171    if (settings->client_cert != NULL) {
    172
    173        /* Read client certificate from configuration data */
    174        X509* client_cert = guac_kubernetes_read_cert(settings->client_cert);
    175        if (client_cert == NULL) {
    176            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    177                    "Provided client certificate is unreadable");
    178            return;
    179        }
    180
    181        /* Use parsed certificate for authentication */
    182        if (!SSL_CTX_use_certificate(context, client_cert)) {
    183            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    184                    "Client certificate could not be used for SSL/TLS "
    185                    "client authentication");
    186            return;
    187        }
    188
    189    }
    190
    191    /* Private key for SSL/TLS client auth */
    192    if (settings->client_key != NULL) {
    193
    194        /* Read client private key from configuration data */
    195        EVP_PKEY* client_key = guac_kubernetes_read_key(settings->client_key);
    196        if (client_key == NULL) {
    197            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    198                    "Provided client private key is unreadable");
    199            return;
    200        }
    201
    202        /* Use parsed key for authentication */
    203        if (!SSL_CTX_use_PrivateKey(context, client_key)) {
    204            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    205                    "Client private key could not be used for SSL/TLS "
    206                    "client authentication");
    207            return;
    208        }
    209
    210    }
    211
    212    /* Enable hostname checking */
    213    X509_VERIFY_PARAM *param = SSL_CTX_get0_param(context);
    214    X509_VERIFY_PARAM_set_hostflags(param,
    215            X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
    216
    217    /* Validate properly depending on whether hostname is an IP address */
    218    if (guac_kubernetes_is_address(settings->hostname)) {
    219        if (!X509_VERIFY_PARAM_set1_ip_asc(param, settings->hostname)) {
    220            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    221                    "Server IP address validation could not be enabled");
    222            return;
    223        }
    224    }
    225    else {
    226        if (!X509_VERIFY_PARAM_set1_host(param, settings->hostname, 0)) {
    227            guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
    228                    "Server hostname validation could not be enabled");
    229            return;
    230        }
    231    }
    232
    233}
    234