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}