Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute default certificate validation when overriding ServicePointManager.ServerCertificateValidationCallback

I use a SmtpClient to send emails through a SMTP server using SSL. This SMTP server is configured to use a self-signed certificate.

Since it's out of scope for me to install the certificate on all client machines, I added a ServicePointManager.ServerCertificateValidationCallback to implement a custom validation for our self-signed certificate.

However, I would like to execute the custom validation only when I'm using the SmtpClient, and revert back to the default behavior when the certificate validation is performed elsewhere.

Since the callback is a static property, I should also make sure that the callback isn't called by another thread than the one I use to send the email.

I came up with the following code:

Private Shared _defaultServerCertificateValidationCallback As System.Net.Security.RemoteCertificateValidationCallback
Private Shared _overrideSmtpCertificateValidation As Boolean = False
Private Shared _callbackLocker As New Object()

Private Shared Function OurCertificateValidation(s As Object, certificate As Security.Cryptography.X509Certificates.X509Certificate, chain As Security.Cryptography.X509Certificates.X509Chain, sslPolicyErrors As Net.Security.SslPolicyErrors) As Boolean

    SyncLock _callbackLocker

        If _overrideSmtpCertificateValidation Then
            _overrideSmtpCertificateValidation = False
            Dim actualCertificate = Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile("selfSignedSmtp.cert")
            Return certificate.Equals(actualCertificate)
        Else
            'Should execute the default certificate validation.
            Return _defaultServerCertificateValidationCallback(s, certificate, chain, sslPolicyErrors)
        End If

    End SyncLock

End Function

Public Sub SendMail()
    _defaultServerCertificateValidationCallback = System.Net.ServicePointManager.ServerCertificateValidationCallback
    System.Net.ServicePointManager.ServerCertificateValidationCallback = New System.Net.Security.RemoteCertificateValidationCallback(AddressOf OurCertificateValidation)
    _overrideSmtpCertificateValidation = True
    'Send mail using SMTP client here...
End Sub

What this code should do when sending an email is to keep the default ServerCertificateValidationCallback in a variable, flag the custom validation to be performed, perform the custom validation on the same thread as the SmtpClient, prevent other threads from performing the custom validation by using SyncLock, then revert back to the default behavior and allow other threads to continue with the default certificate validation.

However, the ServerCertificateValidationCallback is set to Nothing by default, so I cannot explicitly invoke the default validation callback.

Is there something I could invoke instead of Return _defaultServerCertificateValidationCallback(s, certificate, chain, sslPolicyErrors) that would execute the default certificate validation when needed?

like image 203
Benjamin Beaulieu Avatar asked Jul 11 '14 13:07

Benjamin Beaulieu


1 Answers

I found out that the default certificate validation is done prior to the ServerCertificateValidationCallback. If sslPolicyErrors is set to None, it means that the validation was successful.

I think this code fulfills my needs and seems safe enough for my situation:

Private Shared _customCertificateLocker As New Object()
Private Shared _customCertificateThreadId As Integer
Private Shared _customCertificatePath As String

'Returns True if the certificate is valid according to the default certificate validation algorithm,
'or else if the certificate is the same as the specified custom certificate.
Private Shared Function CustomCertificateValidation(s As Object, certificate As Security.Cryptography.X509Certificates.X509Certificate, chain As Security.Cryptography.X509Certificates.X509Chain, sslPolicyErrors As Net.Security.SslPolicyErrors) As Boolean

    If sslPolicyErrors = System.Net.Security.SslPolicyErrors.None Then
        'The certificate is valid according to the default certificate validation algorithm
        Return True
    Else

        'Validates only for the thread that called UseCustomCertificate.
        If System.Threading.Thread.CurrentThread.ManagedThreadId = _customCertificateThreadId Then

            If Not File.Exists(_customCertificatePath) Then
                Throw New FileNotFoundException("Certificate not found at path : '" & _customCertificatePath & "'.")
            End If

            Try
                'Validates the specified custom certificate against the server certificate.
                Dim actualCertificate = Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile(_customCertificatePath)
                Return certificate.Equals(actualCertificate)
            Catch
                Return False
            End Try

        End If

    End If

    Return False

End Function

'Performs an Action that needs the custom certificate validation, on one thread at a time.
Public Shared Sub UseCustomCertificate(customCertificatePath As String, action As Action)

    'Used to make sure that the ServerCertificateValidationCallback stays the same during the Action, even if multiple threads call this method.
    SyncLock _customCertificateLocker

        _customCertificateThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        _customCertificatePath = customCertificatePath
        System.Net.ServicePointManager.ServerCertificateValidationCallback = New System.Net.Security.RemoteCertificateValidationCallback(AddressOf CustomCertificateValidation)

        Try
            action()

        Finally
            _customCertificateThreadId = 0
            _customCertificatePath = String.Empty
            System.Net.ServicePointManager.ServerCertificateValidationCallback = Nothing
        End Try

    End SyncLock

End Sub

Example of usage:

UseCustomCertificate("C:\example.cert",
                     Sub()
                        'Send email with SmtpClient...
                     End Sub)

I will accept this answer only if I don't get a safer/better answer in a couple of days.

like image 129
Benjamin Beaulieu Avatar answered Nov 09 '22 05:11

Benjamin Beaulieu