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