Skip to content

Commit bafc59f

Browse files
committed
libvncclient: tls_gnutls: add expected X.509 fingerprint handling
1 parent 0e4b263 commit bafc59f

File tree

1 file changed

+139
-11
lines changed

1 file changed

+139
-11
lines changed

src/libvncclient/tls_gnutls.c

Lines changed: 139 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,51 @@ static gnutls_dh_params_t rfbDHParams;
3333

3434
static rfbBool rfbTLSInitialized = FALSE;
3535

36+
/* Struct to hold callback data */
37+
typedef struct {
38+
rfbClient *rfbClient;
39+
rfbCredential *rfbCredential;
40+
} TLSCallbackData;
41+
42+
/**
43+
* Callback to handle fingerprint mismatch
44+
*/
45+
static int cert_fingerprint_mismatch_callback(rfbClient *client, gnutls_x509_crt_t cert)
46+
{
47+
if(!cert) {
48+
rfbClientErr("No cert given in fingerprint mismatch handling\n");
49+
return 0;
50+
}
51+
52+
if (!client || !client->GetX509CertFingerprintMismatchDecision) {
53+
rfbClientErr("No client callback given in fingerprint mismatch handling\n");
54+
return 0;
55+
}
56+
57+
/* Subject */
58+
char subject[1024];
59+
size_t subject_size = sizeof(subject);
60+
if (gnutls_x509_crt_get_dn(cert, subject, &subject_size) < 0) {
61+
return 0;
62+
}
63+
64+
/* Validity */
65+
time_t not_before = (time_t)gnutls_x509_crt_get_activation_time(cert);
66+
time_t not_after = (time_t)gnutls_x509_crt_get_expiration_time(cert);
67+
68+
/* SHA-256 fingerprint */
69+
unsigned char fingerprint[32];
70+
size_t fingerprint_size = sizeof(fingerprint);
71+
if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, fingerprint, &fingerprint_size) < 0) {
72+
return 0;
73+
}
74+
75+
/* Call the callback (just reads values) */
76+
rfbBool decision = client->GetX509CertFingerprintMismatchDecision(client, subject, not_before, not_after, fingerprint, 32);
77+
78+
return decision ? 1 : 0;
79+
}
80+
3681
static int
3782
verify_certificate_callback (gnutls_session_t session)
3883
{
@@ -43,8 +88,17 @@ verify_certificate_callback (gnutls_session_t session)
4388
gnutls_x509_crt_t cert;
4489
rfbClient *sptr;
4590
char *hostname;
91+
rfbCredential *cred = NULL;
92+
93+
TLSCallbackData *callback_data = (TLSCallbackData *)gnutls_session_get_ptr(session);
94+
if (!callback_data) {
95+
rfbClientErr("Failed to validate certificate - missing callback data\n");
96+
return GNUTLS_E_CERTIFICATE_ERROR;
97+
}
98+
99+
sptr = callback_data->rfbClient;
100+
cred = callback_data->rfbCredential;
46101

47-
sptr = (rfbClient *)gnutls_session_get_ptr(session);
48102
if (!sptr) {
49103
rfbClientLog("Failed to validate certificate - missing client data\n");
50104
return GNUTLS_E_CERTIFICATE_ERROR;
@@ -81,13 +135,59 @@ verify_certificate_callback (gnutls_session_t session)
81135
if (status & GNUTLS_CERT_NOT_ACTIVATED)
82136
rfbClientLog("The certificate is not yet activated\n");
83137

138+
/* status would be 0 if cert was trusted */
84139
if (status)
85-
return GNUTLS_E_CERTIFICATE_ERROR;
140+
{
141+
if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
142+
rfbClientErr("The certificate was not X509\n");
143+
return GNUTLS_E_CERTIFICATE_ERROR;
144+
}
86145

87-
/* Up to here the process is the same for X.509 certificates and
88-
* OpenPGP keys. From now on X.509 certificates are assumed. This can
89-
* be easily extended to work with openpgp keys as well.
90-
*/
146+
if (gnutls_x509_crt_init (&cert) < 0)
147+
{
148+
rfbClientErr("Error initialising certificate structure\n");
149+
return GNUTLS_E_CERTIFICATE_ERROR;
150+
}
151+
152+
cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
153+
if (cert_list == NULL)
154+
{
155+
rfbClientErr("No certificate was found!\n");
156+
return GNUTLS_E_CERTIFICATE_ERROR;
157+
}
158+
159+
if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
160+
{
161+
rfbClientErr("Error parsing certificate\n");
162+
return GNUTLS_E_CERTIFICATE_ERROR;
163+
}
164+
165+
/* Check if we have an expected fingerprint */
166+
if (cred && cred->x509Credential.x509ExpectedFingerprint) {
167+
unsigned char remote_fingerprint[32];
168+
size_t fingerprint_size = sizeof(remote_fingerprint);
169+
170+
if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, remote_fingerprint, &fingerprint_size) == 0) {
171+
if (memcmp(remote_fingerprint, cred->x509Credential.x509ExpectedFingerprint, 32) == 0) {
172+
rfbClientLog("The certificate's fingerprint matched the expected one - accepting.\n");
173+
gnutls_x509_crt_deinit (cert);
174+
return 0;
175+
}
176+
}
177+
}
178+
179+
/* Fingerprint didn't match or no expected fingerprint - ask user */
180+
if (cert_fingerprint_mismatch_callback(sptr, cert)) {
181+
rfbClientLog("User decided to trust certificate - accepting.\n");
182+
gnutls_x509_crt_deinit (cert);
183+
return 0;
184+
}
185+
186+
gnutls_x509_crt_deinit (cert);
187+
return GNUTLS_E_CERTIFICATE_ERROR;
188+
}
189+
190+
/* Certificate is trusted by system CA or via given rfbCredential.x509Credential.x509CACertfile */
91191
if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) {
92192
rfbClientLog("The certificate was not X509\n");
93193
return GNUTLS_E_CERTIFICATE_ERROR;
@@ -400,6 +500,7 @@ FreeX509Credential(rfbCredential *cred)
400500
if (cred->x509Credential.x509CACrlFile) free(cred->x509Credential.x509CACrlFile);
401501
if (cred->x509Credential.x509ClientCertFile) free(cred->x509Credential.x509ClientCertFile);
402502
if (cred->x509Credential.x509ClientKeyFile) free(cred->x509Credential.x509ClientKeyFile);
503+
if (cred->x509Credential.x509ExpectedFingerprint) free(cred->x509Credential.x509ExpectedFingerprint);
403504
free(cred);
404505
}
405506

@@ -545,10 +646,9 @@ HandleVeNCryptAuth(rfbClient* client)
545646
if (!InitializeTLS()) return FALSE;
546647

547648
/* Get X509 Credentials if it's not anonymous */
649+
rfbCredential *cred = NULL;
548650
if (!anonTLS)
549651
{
550-
rfbCredential *cred;
551-
552652
if (!client->GetCredential)
553653
{
554654
rfbClientLog("GetCredential callback is not set.\n");
@@ -562,8 +662,10 @@ HandleVeNCryptAuth(rfbClient* client)
562662
}
563663

564664
x509_cred = CreateX509CertCredential(cred);
565-
FreeX509Credential(cred);
566-
if (!x509_cred) return FALSE;
665+
if (!x509_cred) {
666+
FreeX509Credential(cred);
667+
return FALSE;
668+
}
567669
}
568670

569671
/* Start up the TLS session */
@@ -577,12 +679,28 @@ HandleVeNCryptAuth(rfbClient* client)
577679
{
578680
/* Set the certificate verification callback. */
579681
gnutls_certificate_set_verify_function (x509_cred, verify_certificate_callback);
580-
gnutls_session_set_ptr ((gnutls_session_t)client->tlsSession, (void *)client);
682+
683+
/* We need the client plus the rfbCredential in the verification callback */
684+
TLSCallbackData *callback_data = malloc(sizeof(TLSCallbackData));
685+
if (!callback_data) {
686+
rfbClientErr("Cannot allocate callback data\n");
687+
FreeTLS(client);
688+
gnutls_certificate_free_credentials(x509_cred);
689+
FreeX509Credential(cred);
690+
return FALSE;
691+
}
692+
callback_data->rfbClient = client;
693+
callback_data->rfbCredential = cred;
694+
695+
/* Store the callback data in the session pointer so the callback can access them */
696+
gnutls_session_set_ptr ((gnutls_session_t)client->tlsSession, (void *)callback_data);
581697

582698
if ((ret = gnutls_credentials_set((gnutls_session_t)client->tlsSession, GNUTLS_CRD_CERTIFICATE, x509_cred)) < 0)
583699
{
584700
rfbClientLog("Cannot set x509 credential: %s.\n", gnutls_strerror(ret));
585701
FreeTLS(client);
702+
gnutls_certificate_free_credentials(x509_cred);
703+
free(callback_data);
586704
return FALSE;
587705
}
588706
}
@@ -645,6 +763,16 @@ void FreeTLS(rfbClient* client)
645763
{
646764
if (client->tlsSession)
647765
{
766+
// Get the callback data from the session pointer before deinitializing
767+
TLSCallbackData *callback_data = (TLSCallbackData *)gnutls_session_get_ptr((gnutls_session_t)client->tlsSession);
768+
// Free the callback data and credentials
769+
if (callback_data) {
770+
if (callback_data->rfbCredential) {
771+
FreeX509Credential(callback_data->rfbCredential);
772+
}
773+
free(callback_data);
774+
}
775+
648776
gnutls_deinit((gnutls_session_t)client->tlsSession);
649777
client->tlsSession = NULL;
650778
TINI_MUTEX(client->tlsRwMutex);

0 commit comments

Comments
 (0)