Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : // Request compatibility with OpenSSL 1.1.1.
14 : // We need this for the hashing API.
15 : #define OPENSSL_API_COMPAT 0x10100000L
16 :
17 : #include "monetdb_config.h"
18 :
19 :
20 : #include "stream.h" /* include before mapi.h */
21 : #include "stream_socket.h"
22 : #include "mapi.h"
23 : #include "mapi_intern.h"
24 :
25 : #include <stdarg.h>
26 : #include <openssl/ssl.h>
27 : #include <openssl/err.h>
28 : #include <openssl/x509v3.h>
29 :
30 : MapiMsg
31 6 : croak_openssl(Mapi mid, const char *action, const char *fmt, ...)
32 : {
33 6 : va_list ap;
34 6 : char buffer[800];
35 6 : va_start(ap, fmt);
36 6 : vsnprintf(buffer, sizeof(buffer), fmt, ap);
37 6 : va_end(ap);
38 :
39 6 : unsigned long err = ERR_get_error();
40 12 : const char *errmsg =
41 : #ifdef ERR_SYSTEM_ERROR
42 6 : ERR_SYSTEM_ERROR(err)
43 0 : ? strerror(ERR_GET_REASON(err))
44 6 : :
45 : #endif
46 6 : ERR_reason_error_string(err);
47 6 : if (errmsg)
48 6 : return mapi_printError(mid, action, MERROR, "TLS error: %s: %s", buffer, errmsg);
49 0 : else if (err != 0)
50 0 : return mapi_printError(mid, action, MERROR, "TLS error: %s: failed with error %lu (0x%lx)", buffer, err, err);
51 : else
52 0 : return mapi_printError(mid, action, MERROR, "TLS error: %s", buffer);
53 : }
54 :
55 : #ifndef NATIVE_WIN32
56 : MapiMsg
57 2 : add_system_certificates(Mapi mid, SSL_CTX *ctx)
58 : {
59 : // On Linux, the Linux distribution has arranged for the system root certificates
60 : // to be found in OpenSSL's default locations.
61 : // On MacOS, MonetDB is generally installed using Homebrew and then Homebrew
62 : // takes care of it.
63 : // On Windows we use another implementation of add_system_certificates(),
64 : // found in openssl_windows.c.
65 :
66 2 : if (1 != SSL_CTX_set_default_verify_paths(ctx)) {
67 0 : SSL_CTX_free(ctx);
68 0 : return croak_openssl(mid, __func__, "SSL_CTX_set_default_verify_paths");
69 : }
70 : return MOK;
71 : }
72 : #endif
73 :
74 : static MapiMsg
75 12 : make_ssl_context(Mapi mid, SSL_CTX **ctx_out)
76 : {
77 : // Today we just create a new one but if we load the system trust store
78 : // the result could be cached for a while.
79 : // (What's a reasonable amount of time for a process to pick up changes to
80 : // the system trust store?)
81 :
82 12 : MapiMsg msg;
83 12 : *ctx_out = NULL;
84 :
85 12 : const SSL_METHOD *method = TLS_method();
86 12 : if (!method)
87 0 : return croak_openssl(mid, __func__, "TLS_method");
88 12 : SSL_CTX *ctx = SSL_CTX_new(method);
89 12 : if (!ctx)
90 0 : return croak_openssl(mid, __func__, "SSL_CTX_new");
91 : // From here on we need to free 'ctx' on failure
92 :
93 12 : SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
94 12 : SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
95 12 : SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION);
96 : // Because we use at least TLSv1.3 we don't need to mess with
97 : // SSL_CTX_set_cipher_list() and SSL_CTX_set_ciphersuites().
98 :
99 12 : const char *cert;
100 12 : switch (msettings_connect_tls_verify(mid->settings)) {
101 3 : case verify_none:
102 : case verify_hash:
103 3 : SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
104 3 : break;
105 7 : case verify_cert:
106 7 : cert = msetting_string(mid->settings, MP_CERT);
107 7 : if (1 != SSL_CTX_load_verify_locations(ctx, cert, NULL)) {
108 0 : SSL_CTX_free(ctx);
109 0 : return croak_openssl(mid, __func__, "SSL_CTX_load_verify_file: %s", cert);
110 : }
111 : break;
112 2 : case verify_system:
113 2 : msg = add_system_certificates(mid, ctx);
114 2 : if (msg != MOK) {
115 0 : SSL_CTX_free(ctx);
116 0 : return msg;
117 : }
118 : break;
119 : }
120 :
121 12 : *ctx_out = ctx;
122 12 : return MOK;
123 : }
124 :
125 : static MapiMsg
126 3 : verify_server_certificate_hash(Mapi mid, X509 *x509, const char *required_prefix)
127 : {
128 3 : mapi_log_record(mid, "CONN", "verifying certificate hash against prefix '%s'", required_prefix);
129 :
130 3 : size_t prefix_len = strlen(required_prefix);
131 3 : if (prefix_len > 2 * SHA256_DIGEST_LENGTH)
132 0 : return mapi_setError(mid, "value of certhash= is longer than a sha256 digest", __func__, MERROR);
133 :
134 : // Convert to DER
135 3 : unsigned char *buf = NULL;
136 3 : int buflen = i2d_X509(x509, &buf);
137 3 : if (buflen <= 0) {
138 0 : return croak_openssl(mid, __func__, "could not convert server certificate to DER");
139 : }
140 3 : assert(buf);
141 :
142 : // Compute the has of the DER using the deprecated API so we stay
143 : // compatible with OpenSSL 1.1.1.
144 3 : SHA256_CTX sha256;
145 3 : if (1 != SHA256_Init(&sha256)) {
146 0 : OPENSSL_free(buf);
147 0 : return mapi_setError(mid, "SHA256_Init", __func__, MERROR);
148 : }
149 3 : if (1 != SHA256_Update(&sha256, buf, buflen)) {
150 0 : OPENSSL_free(buf);
151 0 : return mapi_setError(mid, "SHA256_Update", __func__, MERROR);
152 : }
153 3 : unsigned char digest[SHA256_DIGEST_LENGTH];
154 3 : if (1 != SHA256_Final(digest, &sha256)) {
155 0 : OPENSSL_free(buf);
156 0 : return mapi_setError(mid, "SHA256_Final", __func__, MERROR);
157 : }
158 3 : OPENSSL_free(buf);
159 :
160 : // Make hexadecimal;
161 3 : char hex[2 * SHA256_DIGEST_LENGTH + 1];
162 99 : for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
163 96 : snprintf(&hex[2 * i], 3, "%02x", digest[i]);
164 : }
165 3 : assert(hex[2 * SHA256_DIGEST_LENGTH] == '\0');
166 :
167 : // Compare the digits
168 3 : if (strncmp(required_prefix, hex, prefix_len) != 0)
169 2 : return mapi_setError(mid, "server certificate does not match certhash= prefix", __func__, MERROR);
170 :
171 1 : mapi_log_record(mid, "CONN", "server certificate matches certhash");
172 : return MOK;
173 : }
174 : MapiMsg
175 12 : wrap_tls(Mapi mid, SOCKET sock)
176 : {
177 : // Based on the example on the OpenSSL wiki:
178 : // https://wiki.openssl.org/index.php/SSL/TLS_Client
179 :
180 : // On error: close 'sock'.
181 :
182 12 : MapiMsg msg = MOK;
183 :
184 12 : const msettings *settings = mid->settings;
185 12 : const char *host = msettings_connect_tcp(settings);
186 12 : int port = msettings_connect_port(settings);
187 12 : size_t hostlen = strlen(host);
188 12 : size_t hostportlen = hostlen + 1 + 20;
189 :
190 12 : const char *clientkey = msettings_connect_clientkey(settings);
191 12 : const char *clientcert = msettings_connect_clientcert(settings);
192 12 : enum msetting_tls_verify verify_method = msettings_connect_tls_verify(settings);
193 :
194 : // Clear any earlier errrors
195 12 : do {} while (ERR_get_error() != 0);
196 :
197 12 : SSL_CTX *ctx = NULL;
198 12 : msg = make_ssl_context(mid, &ctx);
199 12 : if (msg != MOK) {
200 0 : closesocket(sock);
201 0 : return msg;
202 : }
203 : // On error: close 'sock' and free 'ctx'.
204 :
205 : /////////////////////////////////////////////////////////////////////
206 : // Create the SSL connection
207 :
208 : // BIO 'bio' represents the whole SSL connection.
209 : // We will read and write plaintext from it.
210 12 : BIO *bio = BIO_new_ssl(ctx, 1);
211 12 : if (bio == NULL) {
212 0 : closesocket(sock);
213 0 : SSL_CTX_free(ctx);
214 0 : return croak_openssl(mid, __func__, "BIO_new_ssl");
215 : }
216 : // BIO_new_ssl() inc'd the reference count of ctx so we can drop our
217 : // reference here.
218 12 : SSL_CTX_free(ctx);
219 : // On error: close 'sock' and free 'bio'
220 :
221 12 : SSL *ssl = NULL;
222 12 : if (1 != BIO_get_ssl(bio, &ssl)) {
223 0 : closesocket(sock);
224 0 : BIO_free(bio);
225 0 : return croak_openssl(mid, __func__, "BIO_get_ssl");
226 : }
227 : // As far as I know the SSL returned by BIO_get_ssl has not had
228 : // its refcount inc'd so we don't need to free it.
229 : // On error: close 'sock' and free 'bio'.
230 12 : assert(ssl != NULL);
231 :
232 : // BIO 'sockbio' wraps the socket. OpenSSL will read and write
233 : // ciphertext from it.
234 : //
235 : // The OpenSSL developers apparently believe that it's safe to cast
236 : // SOCKET to int here, see https://github.com/openssl/openssl/blob/openssl-3.1.3/include/internal/sockets.h#L54-L58
237 12 : BIO *sockbio = BIO_new_socket((int)sock, BIO_CLOSE);
238 12 : if (sockbio == NULL) {
239 0 : closesocket(sock);
240 0 : BIO_free_all(bio);
241 0 : return croak_openssl(mid, __func__, "BIO_new_socket");
242 : }
243 : // From here on, 'sock' will be free'd by 'sockbio'.
244 : // On error: free 'sockbio' and free 'bio'.
245 :
246 12 : if (!BIO_up_ref(sockbio)) {
247 0 : BIO_free_all(sockbio);
248 0 : BIO_free_all(bio);
249 0 : return croak_openssl(mid, __func__, "BIO_up_ref sockbio");
250 : }
251 12 : SSL_set0_rbio(ssl, sockbio); // consumes first ref
252 12 : SSL_set0_wbio(ssl, sockbio); // consumes second ref
253 : // from here on 'sockbio' will be freed through 'ssl' which is freed through 'bio'.
254 : // On error: free 'bio'.
255 :
256 12 : if (!SSL_set_tlsext_host_name(ssl, host)) {
257 0 : BIO_free_all(bio);
258 0 : return croak_openssl(mid, __func__, "SSL_set_tlsext_host_name");
259 : }
260 :
261 12 : X509_VERIFY_PARAM *param = SSL_get0_param(ssl);
262 12 : if (param == NULL) {
263 0 : BIO_free_all(bio);
264 0 : return croak_openssl(mid, __func__, "SSL_get0_param");
265 : }
266 12 : X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
267 12 : if (1 != X509_VERIFY_PARAM_set1_host(param, host, strlen(host))) {
268 0 : BIO_free_all(bio);
269 0 : return croak_openssl(mid, __func__, "X509_VERIFY_PARAM_set1_host");
270 : }
271 :
272 : // Temporarily disable the ALPN header.
273 : // TODO re-enable it when test systemcertificates.py no longer relies
274 : // on connecting to an HTTPS server. (Which is an ugly hack in the first place!)
275 : #if 0
276 : unsigned char alpn_vector[] = { 6, 'm', 'a', 'p', 'i', '/', '9' };
277 : // NOTE: these functions return 0 on success, not 1!
278 : if (0 != SSL_set_alpn_protos(ssl, alpn_vector, sizeof(alpn_vector))) {
279 : BIO_free_all(bio);
280 : return croak_openssl(mid, __func__, "SSL_set_alpn_protos");
281 : }
282 : #endif
283 :
284 12 : assert(clientkey);
285 12 : assert(clientcert);
286 12 : if (clientkey[0]) {
287 1 : if (1 != SSL_use_PrivateKey_file(ssl, clientkey, SSL_FILETYPE_PEM)) {
288 0 : BIO_free_all(bio);
289 0 : return croak_openssl(mid, __func__, "SSL_use_PrivateKey_file");
290 : }
291 1 : if (1 != SSL_use_certificate_chain_file(ssl, clientcert)) {
292 0 : BIO_free_all(bio);
293 0 : return croak_openssl(mid, __func__, "SSL_use_certificate_chain_file");
294 : }
295 : }
296 :
297 : // Handshake.
298 12 : if (1 != SSL_connect(ssl)) {
299 6 : BIO_free_all(bio);
300 6 : return croak_openssl(mid, __func__, "SSL_connect handshake");
301 : }
302 :
303 : // Verify the server certificate
304 6 : X509 *server_cert = SSL_get_peer_certificate(ssl);
305 6 : if (server_cert == NULL) {
306 0 : BIO_free_all(bio);
307 0 : return croak_openssl(mid, __func__, "Server did not send a certificate");
308 : }
309 : // be careful when to free server_cert
310 6 : if (verify_method == verify_hash) {
311 3 : const char *required_prefix = msettings_connect_certhash_digits(settings);
312 3 : msg = verify_server_certificate_hash(mid, server_cert, required_prefix);
313 3 : X509_free(server_cert);
314 3 : if (msg != MOK) {
315 2 : BIO_free_all(bio);
316 2 : return msg;
317 : }
318 : } else {
319 3 : X509_free(server_cert);
320 3 : long verify_result = SSL_get_verify_result(ssl);
321 3 : if (verify_result != X509_V_OK) {
322 0 : BIO_free_all(bio);
323 0 : const char *error_message = X509_verify_cert_error_string(verify_result);
324 0 : return croak_openssl(mid, __func__, "Invalid server certificate: %s", error_message);
325 : }
326 : }
327 :
328 : /////////////////////////////////////////////////////////////////////
329 : // Attach the connection to 'mid'
330 :
331 4 : if (!BIO_up_ref(bio)) {
332 0 : BIO_free_all(bio);
333 0 : return croak_openssl(mid, __func__, "BIO_up_ref bio");
334 : }
335 : // On error: free 'bio' twice
336 :
337 4 : char *hostcolonport = malloc(hostportlen);
338 4 : if (hostcolonport != NULL)
339 4 : snprintf(hostcolonport, hostportlen, "%s:%d", host, port);
340 :
341 4 : stream *rstream = openssl_rstream(hostcolonport ? hostcolonport : "ssl rstream", bio);
342 4 : if (rstream == NULL || mnstr_errnr(rstream) != MNSTR_NO__ERROR) {
343 0 : BIO_free_all(bio); // drops first ref
344 0 : BIO_free_all(bio); // drops second ref
345 0 : free(hostcolonport);
346 0 : msg = croak_openssl(mid, __func__, "openssl_rstream: %s", mnstr_peek_error(rstream));
347 0 : close_stream(rstream);
348 0 : return msg;
349 : }
350 : // On error: free 'bio' and close 'rstream'.
351 8 : stream *wstream = openssl_wstream(hostcolonport ? hostcolonport : "ssl wstream", bio);
352 4 : free(hostcolonport);
353 4 : if (wstream == NULL || mnstr_errnr(wstream) != MNSTR_NO__ERROR) {
354 0 : BIO_free_all(bio);
355 0 : close_stream(rstream);
356 0 : msg = croak_openssl(mid, __func__, "openssl_wstream: %s", mnstr_peek_error(wstream));
357 0 : close_stream(wstream);
358 0 : return msg;
359 : }
360 : // On error: free 'rstream' and 'wstream'.
361 4 : msg = mapi_wrap_streams(mid, rstream, wstream);
362 4 : if (msg != MOK) {
363 0 : close_stream(rstream);
364 0 : close_stream(wstream);
365 0 : return msg;
366 : }
367 : // 'rstream' and 'wstream' are part of 'mid' now.
368 :
369 :
370 4 : mapi_log_record(mid, "CONN", "TLS handshake succeeded");
371 : return MOK;
372 : }
|