diff --git a/include/freerdp/crypto/certificate.h b/include/freerdp/crypto/certificate.h index fec950e38..9038ac87d 100644 --- a/include/freerdp/crypto/certificate.h +++ b/include/freerdp/crypto/certificate.h @@ -50,6 +50,7 @@ struct rdp_certificate_store FREERDP_API rdpCertificateData* certificate_data_new(char* hostname, char* fingerprint); FREERDP_API void certificate_data_free(rdpCertificateData* certificate_data); FREERDP_API rdpCertificateStore* certificate_store_new(rdpSettings* settings); +FREERDP_API void certificate_data_replace(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data); FREERDP_API void certificate_store_free(rdpCertificateStore* certificate_store); FREERDP_API int certificate_data_match(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data); FREERDP_API void certificate_data_print(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data); diff --git a/include/freerdp/freerdp.h b/include/freerdp/freerdp.h index e6830855a..8644495b5 100644 --- a/include/freerdp/freerdp.h +++ b/include/freerdp/freerdp.h @@ -52,6 +52,7 @@ typedef boolean (*pPreConnect)(freerdp* instance); typedef boolean (*pPostConnect)(freerdp* instance); typedef boolean (*pAuthenticate)(freerdp* instance, char** username, char** password, char** domain); typedef boolean (*pVerifyCertificate)(freerdp* instance, char* subject, char* issuer, char* fingerprint); +typedef boolean (*pVerifyChangedCertificate)(freerdp* instance, char* subject, char* issuer, char* new_fingerprint, char* old_fingerprint); typedef int (*pSendChannelData)(freerdp* instance, int channelId, uint8* data, int size); typedef int (*pReceiveChannelData)(freerdp* instance, int channelId, uint8* data, int size, int flags, int total_size); @@ -163,7 +164,11 @@ struct rdp_freerdp pVerifyCertificate VerifyCertificate; /**< (offset 51) Callback for certificate validation. Used to verify that an unknown certificate is trusted. */ - uint32 paddingD[64 - 52]; /* 52 */ + pVerifyChangedCertificate VerifyChangedCertificate; /**< (offset 52) + Callback for changed certificate validation. + Used when a certificate differs from stored fingerprint. + If returns true, the new fingerprint will be trusted and old thrown out. */ + uint32 paddingD[64 - 51]; /* 51 */ pSendChannelData SendChannelData; /* (offset 64) Callback for sending data to a channel. diff --git a/libfreerdp-crypto/certificate.c b/libfreerdp-crypto/certificate.c index 1614f1291..da08ff7cf 100644 --- a/libfreerdp-crypto/certificate.c +++ b/libfreerdp-crypto/certificate.c @@ -129,6 +129,68 @@ int certificate_data_match(rdpCertificateStore* certificate_store, rdpCertificat return match; } +void certificate_data_replace(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data) +{ + FILE* fp; + int length; + char* data; + char* pline; + long int size; + + fp = certificate_store->fp; + + if (!fp) + return; + + // Read the current contents of the file. + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (size < 1) + return; + + data = (char*) xmalloc(size + 2); + + if (fread(data, size, 1, fp) != 1) + { + xfree(data); + return; + } + + // Write the file back out, with appropriate fingerprint substitutions + fp = fopen(certificate_store->file, "w+"); + data[size] = '\n'; + data[size + 1] = '\0'; + pline = strtok(data, "\n"); // xxx: use strsep + + while (pline != NULL) + { + length = strlen(pline); + + if (length > 0) + { + char* hostname = pline, *fingerprint; + + length = strcspn(pline, " \t"); + hostname[length] = '\0'; + + /* If this is the replaced hostname, use the updated fingerprint. */ + if (strcmp(hostname, certificate_data->hostname) == 0) + fingerprint = certificate_data->fingerprint; + else + fingerprint = &hostname[length + 1]; + + fprintf(fp, "%s %s\n", hostname, fingerprint); + } + + pline = strtok(NULL, "\n"); + } + + fclose(fp); + xfree(data); +} + void certificate_data_print(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data) { FILE* fp; diff --git a/libfreerdp-crypto/tls.c b/libfreerdp-crypto/tls.c index f7614b0d6..180099f75 100644 --- a/libfreerdp-crypto/tls.c +++ b/libfreerdp-crypto/tls.c @@ -434,6 +434,7 @@ boolean tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname) char* issuer; char* subject; char* fingerprint; + freerdp* instance = (freerdp*) tls->settings->instance; boolean accept_certificate = false; issuer = crypto_cert_issuer(cert->px509); @@ -446,9 +447,6 @@ boolean tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname) if (match == 1) { /* no entry was found in known_hosts file, prompt user for manual verification */ - - freerdp* instance = (freerdp*) tls->settings->instance; - if (!hostname_match) tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count); @@ -469,9 +467,23 @@ boolean tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname) } else if (match == -1) { - /* entry was found in known_hosts file, but fingerprint does not match */ + /* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */ tls_print_certificate_error(hostname, fingerprint); - verification_status = false; /* failure! */ + + if (instance->VerifyChangedCertificate) + accept_certificate = instance->VerifyChangedCertificate(instance, subject, issuer, fingerprint, ""); + + if (!accept_certificate) + { + /* user did not accept, abort and do not change known_hosts file */ + verification_status = false; /* failure! */ + } + else + { + /* user accepted new certificate, add replace fingerprint for this host in known_hosts file */ + certificate_data_replace(tls->certificate_store, certificate_data); + verification_status = true; /* success! */ + } } else if (match == 0) { @@ -518,9 +530,6 @@ void tls_print_certificate_error(char* hostname, char* fingerprint) printf("It is also possible that a host key has just been changed.\n"); printf("The fingerprint for the host key sent by the remote host is\n%s\n", fingerprint); printf("Please contact your system administrator.\n"); - printf("Add correct host key in ~/.freerdp/known_hosts to get rid of this message.\n"); - printf("Host key for %s has changed and you have requested strict checking.\n", hostname); - printf("Host key verification failed.\n"); } void tls_print_certificate_name_mismatch_error(char* hostname, char* common_name, char** alt_names, int alt_names_count)