Выполните проверку сертификата по умолчанию при переопределении ServicePointManager.ServerCertificateValidationCallback

я использую SmtpClient для отправки электронной почты через SMTP-сервер с помощью SSL. Этот SMTP-сервер настроен на использование самозаверяющего сертификата.

поскольку я не могу установить сертификат на всех клиентских машинах, я добавил ServicePointManager.ServerCertificateValidationCallback реализовать пользовательскую проверку для нашего самозаверяющего сертификата.

однако, я хотел бы выполнить пользовательскую проверку только тогда, когда я использую SmtpClient, и вернуться к поведению по умолчанию, когда сертификат проверка выполняется в другом месте.

поскольку обратный вызов является статическим свойством, я также должен убедиться, что обратный вызов не вызывается другим потоком, чем тот, который я использую для отправки электронной почты.

я придумал следующий код:

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

что этот код должен делать при отправке электронной почты, чтобы сохранить значение по умолчанию ServerCertificateValidationCallback в переменной отметьте пользовательскую проверку, которую необходимо выполнить, выполните пользовательскую проверку в том же потоке, что и SmtpClient, запретить другим потокам выполнять пользовательскую проверку с помощью SyncLock, затем вернитесь к поведению по умолчанию и разрешите другим потокам продолжить проверку сертификата по умолчанию.

однако ServerCertificateValidationCallback установлено значение Nothing по умолчанию, поэтому я не могу явно вызвать обратный вызов проверки по умолчанию.

есть ли что-то, что я мог бы вызвать вместо Return _defaultServerCertificateValidationCallback(s, certificate, chain, sslPolicyErrors) что бы выполнить проверку сертификата по умолчанию, когда это необходимо?

1 ответов


я узнал, что проверка сертификата по умолчанию осуществляется до ServerCertificateValidationCallback. Если sslPolicyErrors установлено значение None, это означает, что проверка прошла успешно.

Я думаю, что этот код удовлетворяет мои потребности и кажется достаточно безопасным для моей ситуации:

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

пример использования:

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

Я приму этот ответ, только если я не получу более безопасный / лучший ответ через пару дней.