2014-07-11 8 views
5

Io uso un SmtpClient per inviare e-mail tramite un server SMTP utilizzando SSL. Questo server SMTP è configurato per utilizzare un certificato autofirmato.Esegui convalida del certificato predefinito quando si esegue l'override di ServicePointManager.ServerCertificateValidationCallback

Dato che non è possibile installare il certificato su tutti i computer client, ho aggiunto uno ServicePointManager.ServerCertificateValidationCallback per implementare una convalida personalizzata per il nostro certificato autofirmato.

Tuttavia, desidero eseguire la convalida personalizzata solo quando utilizzo lo SmtpClient e ripristino il comportamento predefinito quando la convalida del certificato viene eseguita altrove.

Dal momento che il callback è una struttura statica, vorrei anche fare in modo che la richiamata non viene chiamato da un altro thread di quello che uso per inviare l'e-mail.

mi si avvicinò con il seguente codice:

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 

Ciò che questo codice dovrebbe fare quando si invia una e-mail è quello di mantenere il default ServerCertificateValidationCallback in una variabile, la bandiera di convalida personalizzato da eseguire, eseguire la convalida personalizzato sullo stesso thread del SmtpClient, impedire altri thread di eseguire la convalida personalizzato utilizzando SyncLock, poi ritornano al comportamento predefinito e consentire altri thread di continuare con la convalida certificato predefinito.

Tuttavia, lo ServerCertificateValidationCallback è impostato su Nothing per impostazione predefinita, pertanto non è possibile richiamare esplicitamente il callback di convalida predefinito.

C'è qualcosa che potrei invocare al posto di Return _defaultServerCertificateValidationCallback(s, certificate, chain, sslPolicyErrors) che eseguire la convalida del certificato di default in caso di necessità?

risposta

6

ho scoperto che la convalida del certificato predefinito viene fatta prima di ServerCertificateValidationCallback. Se sslPolicyErrors è impostato su None, significa che la convalida ha avuto esito positivo.

Penso che questo codice soddisfa le mie esigenze e sembra abbastanza sicuro per la mia situazione:

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 

Esempio di utilizzo:

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

accetterò questa risposta solo se non ottengo un risposta più sicura/migliore in un paio di giorni.