Skip to content

Commit 77fb6c0

Browse files
committed
libvncclient: tls_openssl: add expected X.509 fingerprint handling
...for self-signed certs.
1 parent 20671c9 commit 77fb6c0

File tree

1 file changed

+111
-2
lines changed

1 file changed

+111
-2
lines changed

src/libvncclient/tls_openssl.c

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
#endif
3535

3636
static rfbBool rfbTLSInitialized = FALSE;
37+
static int rfbTLSExpectedFingerprintIndex = -1;
38+
static int rfbTLSClientIndex = -1;
3739

3840
// Locking callbacks are only initialized if we have mutex support.
3941
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) || defined(LIBVNCSERVER_HAVE_WIN32THREADS)
@@ -154,6 +156,9 @@ InitializeTLS(void)
154156
SSLeay_add_ssl_algorithms();
155157
RAND_load_file("/dev/urandom", 1024);
156158

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+
157162
rfbClientLog("OpenSSL version %s initialized.\n", SSLeay_version(SSLEAY_VERSION));
158163
rfbTLSInitialized = TRUE;
159164
return TRUE;
@@ -221,6 +226,104 @@ load_crls_from_file(char *file, SSL_CTX *ssl_ctx)
221226
return FALSE;
222227
}
223228

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+
224327
static SSL *
225328
open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredential *cred)
226329
{
@@ -286,7 +389,7 @@ open_ssl_connection (rfbClient *client, int sockfd, rfbBool anonTLS, rfbCredenti
286389
}
287390
}
288391

289-
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
392+
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, cert_verify_callback);
290393

291394
if (verify_crls == rfbX509CrlVerifyClient)
292395
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
325428
}
326429

327430
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+
}
329437

330438
do
331439
{
@@ -526,6 +634,7 @@ FreeX509Credential(rfbCredential *cred)
526634
free(cred->x509Credential.x509CACrlFile);
527635
free(cred->x509Credential.x509ClientCertFile);
528636
free(cred->x509Credential.x509ClientKeyFile);
637+
free(cred->x509Credential.x509ExpectedFingerprint);
529638
free(cred);
530639
}
531640

0 commit comments

Comments
 (0)