| /* |
| * 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 "kubernetes.h" |
| #include "settings.h" |
| |
| #include <guacamole/client.h> |
| #include <openssl/asn1.h> |
| #include <openssl/bio.h> |
| #include <openssl/pem.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509v3.h> |
| #include <openssl/x509_vfy.h> |
| |
| /** |
| * Tests whether the given hostname is, in fact, an IP address. |
| * |
| * @param hostname |
| * The hostname to test. |
| * |
| * @return |
| * Non-zero if the given hostname is an IP address, zero otherwise. |
| */ |
| static int guac_kubernetes_is_address(const char* hostname) { |
| |
| /* Attempt to interpret the hostname as an IP address */ |
| ASN1_OCTET_STRING* ip = a2i_IPADDRESS(hostname); |
| |
| /* If unsuccessful, the hostname is not an IP address */ |
| if (ip == NULL) |
| return 0; |
| |
| /* Converted hostname must be freed */ |
| ASN1_OCTET_STRING_free(ip); |
| return 1; |
| |
| } |
| |
| /** |
| * Parses the given PEM certificate, returning a new OpenSSL X509 structure |
| * representing that certificate. |
| * |
| * @param pem |
| * The PEM certificate. |
| * |
| * @return |
| * An X509 structure representing the given certificate, or NULL if the |
| * certificate was unreadable. |
| */ |
| static X509* guac_kubernetes_read_cert(char* pem) { |
| |
| /* Prepare a BIO which provides access to the in-memory CA cert */ |
| BIO* bio = BIO_new_mem_buf(pem, -1); |
| if (bio == NULL) |
| return NULL; |
| |
| /* Read the CA cert as PEM */ |
| X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL); |
| if (certificate == NULL) { |
| BIO_free(bio); |
| return NULL; |
| } |
| |
| return certificate; |
| |
| } |
| |
| /** |
| * Parses the given PEM private key, returning a new OpenSSL EVP_PKEY structure |
| * representing that key. |
| * |
| * @param pem |
| * The PEM private key. |
| * |
| * @return |
| * An EVP_KEY representing the given private key, or NULL if the private |
| * key was unreadable. |
| */ |
| static EVP_PKEY* guac_kubernetes_read_key(char* pem) { |
| |
| /* Prepare a BIO which provides access to the in-memory key */ |
| BIO* bio = BIO_new_mem_buf(pem, -1); |
| if (bio == NULL) |
| return NULL; |
| |
| /* Read the private key as PEM */ |
| EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); |
| if (key == NULL) { |
| BIO_free(bio); |
| return NULL; |
| } |
| |
| return key; |
| |
| } |
| |
| /** |
| * OpenSSL certificate verification callback which universally accepts all |
| * certificates without performing any verification at all. |
| * |
| * @param x509_ctx |
| * The current context of the certificate verification process. This |
| * parameter is ignored by this particular implementation of the callback. |
| * |
| * @param arg |
| * The arbitrary value passed to SSL_CTX_set_cert_verify_callback(). This |
| * parameter is ignored by this particular implementation of the callback. |
| * |
| * @return |
| * Strictly 0 if certificate verification fails, 1 if the certificate is |
| * verified. No other values are legal return values for this callback as |
| * documented by OpenSSL. |
| */ |
| static int guac_kubernetes_assume_cert_ok(X509_STORE_CTX* x509_ctx, void* arg) { |
| return 1; |
| } |
| |
| void guac_kubernetes_init_ssl(guac_client* client, SSL_CTX* context) { |
| |
| guac_kubernetes_client* kubernetes_client = |
| (guac_kubernetes_client*) client->data; |
| |
| guac_kubernetes_settings* settings = kubernetes_client->settings; |
| |
| /* Bypass certificate checks if requested */ |
| if (settings->ignore_cert) { |
| SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL); |
| SSL_CTX_set_cert_verify_callback(context, |
| guac_kubernetes_assume_cert_ok, NULL); |
| } |
| |
| /* Otherwise use the given CA certificate to validate (if any) */ |
| else if (settings->ca_cert != NULL) { |
| |
| /* Read CA certificate from configuration data */ |
| X509* ca_cert = guac_kubernetes_read_cert(settings->ca_cert); |
| if (ca_cert == NULL) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Provided CA certificate is unreadable"); |
| return; |
| } |
| |
| /* Add certificate to CA store */ |
| X509_STORE* ca_store = SSL_CTX_get_cert_store(context); |
| if (!X509_STORE_add_cert(ca_store, ca_cert)) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Unable to add CA certificate to certificate store of " |
| "SSL context"); |
| return; |
| } |
| |
| } |
| |
| /* Certificate for SSL/TLS client auth */ |
| if (settings->client_cert != NULL) { |
| |
| /* Read client certificate from configuration data */ |
| X509* client_cert = guac_kubernetes_read_cert(settings->client_cert); |
| if (client_cert == NULL) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Provided client certificate is unreadable"); |
| return; |
| } |
| |
| /* Use parsed certificate for authentication */ |
| if (!SSL_CTX_use_certificate(context, client_cert)) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Client certificate could not be used for SSL/TLS " |
| "client authentication"); |
| return; |
| } |
| |
| } |
| |
| /* Private key for SSL/TLS client auth */ |
| if (settings->client_key != NULL) { |
| |
| /* Read client private key from configuration data */ |
| EVP_PKEY* client_key = guac_kubernetes_read_key(settings->client_key); |
| if (client_key == NULL) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Provided client private key is unreadable"); |
| return; |
| } |
| |
| /* Use parsed key for authentication */ |
| if (!SSL_CTX_use_PrivateKey(context, client_key)) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Client private key could not be used for SSL/TLS " |
| "client authentication"); |
| return; |
| } |
| |
| } |
| |
| /* Enable hostname checking */ |
| X509_VERIFY_PARAM *param = SSL_CTX_get0_param(context); |
| X509_VERIFY_PARAM_set_hostflags(param, |
| X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); |
| |
| /* Validate properly depending on whether hostname is an IP address */ |
| if (guac_kubernetes_is_address(settings->hostname)) { |
| if (!X509_VERIFY_PARAM_set1_ip_asc(param, settings->hostname)) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Server IP address validation could not be enabled"); |
| return; |
| } |
| } |
| else { |
| if (!X509_VERIFY_PARAM_set1_host(param, settings->hostname, 0)) { |
| guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Server hostname validation could not be enabled"); |
| return; |
| } |
| } |
| |
| } |
| |