|
34 | 34 | #endif |
35 | 35 |
|
36 | 36 | static rfbBool rfbTLSInitialized = FALSE; |
| 37 | +static int rfbTLSExpectedFingerprintIndex = -1; |
| 38 | +static int rfbTLSClientIndex = -1; |
37 | 39 |
|
38 | 40 | // Locking callbacks are only initialized if we have mutex support. |
39 | 41 | #if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) || defined(LIBVNCSERVER_HAVE_WIN32THREADS) |
@@ -154,6 +156,9 @@ InitializeTLS(void) |
154 | 156 | SSLeay_add_ssl_algorithms(); |
155 | 157 | RAND_load_file("/dev/urandom", 1024); |
156 | 158 |
|
| 159 | + rfbTLSExpectedFingerprintIndex = SSL_get_ex_new_index(0, "rfbTLSExpectedFingerprintIndex", NULL, NULL, NULL); |
| 160 | + rfbTLSClientIndex = SSL_get_ex_new_index(0, "rfbTLSClientIndex", NULL, NULL, NULL); |
| 161 | + |
157 | 162 | rfbClientLog("OpenSSL version %s initialized.\n", SSLeay_version(SSLEAY_VERSION)); |
158 | 163 | rfbTLSInitialized = TRUE; |
159 | 164 | return TRUE; |
@@ -221,6 +226,104 @@ load_crls_from_file(char *file, SSL_CTX *ssl_ctx) |
221 | 226 | return FALSE; |
222 | 227 | } |
223 | 228 |
|
| 229 | +/** Convert ASN1_TIME to time_t */ |
| 230 | +static time_t asn1time_to_time_t(const ASN1_TIME *t) { |
| 231 | + struct tm tm; |
| 232 | + memset(&tm, 0, sizeof(tm)); |
| 233 | + |
| 234 | + // If t is NULL, ASN1_TIME_to_tm() converts the current time, which is not what we want. |
| 235 | + if (!t || !ASN1_TIME_to_tm(t, &tm)) { |
| 236 | + return (time_t)-1; |
| 237 | + } |
| 238 | + |
| 239 | + return mktime(&tm); // time_t in localtime; for UTC, use timegm if available |
| 240 | +} |
| 241 | + |
| 242 | +static int cert_fingerprint_mismatch_callback(rfbClient *client, X509 *cert) { |
| 243 | + if(!cert) { |
| 244 | + rfbClientErr("No cert given in fingerprint mismatch handling\n"); |
| 245 | + return 0; |
| 246 | + } |
| 247 | + |
| 248 | + if (!client || !client->GetX509CertFingerprintMismatchDecision) { |
| 249 | + rfbClientErr("No client callback given in fingerprint mismatch handling\n"); |
| 250 | + return 0; |
| 251 | + } |
| 252 | + |
| 253 | + /* Subject */ |
| 254 | + BIO *bio = BIO_new(BIO_s_mem()); |
| 255 | + if (!bio) { |
| 256 | + return 0; |
| 257 | + } |
| 258 | + X509_NAME_print_ex(bio, X509_get_subject_name(cert), 0, XN_FLAG_RFC2253); |
| 259 | + char *data; |
| 260 | + long data_len = BIO_get_mem_data(bio, &data); // owned by OpenSSL |
| 261 | + if (data_len <= 0) { |
| 262 | + BIO_free(bio); |
| 263 | + return 0; |
| 264 | + } |
| 265 | + // Allocate a null-terminated copy, bio does not have that |
| 266 | + char *subject = malloc(data_len + 1); |
| 267 | + if (subject) { |
| 268 | + memcpy(subject, data, data_len); |
| 269 | + subject[data_len] = '\0'; |
| 270 | + } |
| 271 | + BIO_free(bio); |
| 272 | + |
| 273 | + /* Validity */ |
| 274 | + time_t not_before = asn1time_to_time_t(X509_get0_notBefore(cert)); |
| 275 | + time_t not_after = asn1time_to_time_t(X509_get0_notAfter(cert)); |
| 276 | + |
| 277 | + /* SHA-256 fingerprint */ |
| 278 | + unsigned char fingerprint[32]; |
| 279 | + if (! X509_digest(cert, EVP_sha256(), fingerprint, NULL)) { |
| 280 | + free(subject); |
| 281 | + return 0; |
| 282 | + } |
| 283 | + |
| 284 | + /* Call the callback (just reads values) */ |
| 285 | + rfbBool decision = client->GetX509CertFingerprintMismatchDecision(client, subject, not_before, not_after, fingerprint, 32); |
| 286 | + |
| 287 | + /* Free all allocated memory here */ |
| 288 | + free(subject); |
| 289 | + |
| 290 | + return decision ? 1 : 0; |
| 291 | +} |
| 292 | + |
| 293 | +static int cert_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { |
| 294 | + if (preverify_ok) { |
| 295 | + rfbClientLog("Server cert trusted\n"); |
| 296 | + return 1; // Accept |
| 297 | + } |
| 298 | + |
| 299 | + /* |
| 300 | + If preverify failed, compare fingerprints |
| 301 | + */ |
| 302 | + unsigned char remote_fingerprint[32]; |
| 303 | + if (! X509_digest(X509_STORE_CTX_get_current_cert(ctx), EVP_sha256(), remote_fingerprint, NULL)) { |
| 304 | + return 0; |
| 305 | + } |
| 306 | + |
| 307 | + SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); |
| 308 | + const unsigned char *expected_fingerprint = SSL_get_ex_data(ssl, rfbTLSExpectedFingerprintIndex); |
| 309 | + |
| 310 | + int verify; |
| 311 | + if (expected_fingerprint && memcmp(remote_fingerprint, expected_fingerprint, 32) == 0) { |
| 312 | + // accept |
| 313 | + verify = 1; |
| 314 | + } else { |
| 315 | + // ask user |
| 316 | + verify = cert_fingerprint_mismatch_callback(SSL_get_ex_data(ssl, rfbTLSClientIndex), X509_STORE_CTX_get_current_cert(ctx)); |
| 317 | + } |
| 318 | + |
| 319 | + if(verify) { |
| 320 | + X509_STORE_CTX_set_error(ctx, X509_V_OK); |
| 321 | + } |
| 322 | + |
| 323 | + return verify; |
| 324 | +} |
| 325 | + |
| 326 | + |
224 | 327 | static SSL * |
225 | 328 | open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredential *cred) |
226 | 329 | { |
@@ -286,7 +389,7 @@ open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredenti |
286 | 389 | } |
287 | 390 | } |
288 | 391 |
|
289 | | - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); |
| 392 | + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, cert_verify_callback); |
290 | 393 |
|
291 | 394 | if (verify_crls == rfbX509CrlVerifyClient) |
292 | 395 | X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); |
@@ -325,7 +428,12 @@ open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredenti |
325 | 428 | } |
326 | 429 |
|
327 | 430 | SSL_set_fd (ssl, sockfd); |
328 | | - SSL_CTX_set_app_data (ssl_ctx, client); |
| 431 | + SSL_set_ex_data(ssl, rfbTLSClientIndex, client); |
| 432 | + |
| 433 | + if (!anonTLS && cred->x509Credential.x509ExpectedFingerprint) { |
| 434 | + // store expected fingerprint |
| 435 | + SSL_set_ex_data(ssl, rfbTLSExpectedFingerprintIndex, cred->x509Credential.x509ExpectedFingerprint); |
| 436 | + } |
329 | 437 |
|
330 | 438 | do |
331 | 439 | { |
@@ -526,6 +634,7 @@ FreeX509Credential(rfbCredential *cred) |
526 | 634 | free(cred->x509Credential.x509CACrlFile); |
527 | 635 | free(cred->x509Credential.x509ClientCertFile); |
528 | 636 | free(cred->x509Credential.x509ClientKeyFile); |
| 637 | + free(cred->x509Credential.x509ExpectedFingerprint); |
529 | 638 | free(cred); |
530 | 639 | } |
531 | 640 |
|
|
0 commit comments