Imports System
Imports System.Collections
Imports System.Text
Imports ComponentPro.Net
Imports ComponentPro.Security.Certificates

Namespace Pop3Client.Security
	Friend Class CustomCertValidator
		Implements ICertificateValidator
		Private Shared ReadOnly acceptedCertificates As New Hashtable()

		Private Function GetCertProblem(ByVal res As CertificateValidationResult, ByVal status As CertificateValidationStatus, ByRef addToTrustedListForm As Boolean) As String
			Select Case status
				Case CertificateValidationStatus.TimeNotValid
					Return "Server certificate has expired or is not valid yet."

				Case CertificateValidationStatus.Revoked
					Return "Server certificate has been revoked."

				Case CertificateValidationStatus.UnknownCA
					Return "Server certificate was issued by an unknown authority."

				Case CertificateValidationStatus.RootNotTrusted
					addToTrustedListForm = True
					Return "Server certificate was issued by an untrusted authority."

				Case CertificateValidationStatus.IncompleteChain
					Return "Server certificate does not chain up to a trusted root authority."

				Case CertificateValidationStatus.Malformed
					Return "Server certificate is malformed."

				Case CertificateValidationStatus.CNNotMatch
					Return "Server hostname does not match the certificate."

				Case CertificateValidationStatus.UnknownError
					Return String.Format("Error {0:x} encountered while validating server's certificate.", res.ErrorCode)

				Case Else
					Return status.ToString()
			End Select
		End Function

		Public Function Validate(ByVal info As CertificateValidatorRequestInfo) As TlsSslCertificateAcceptance
			Dim res As CertificateValidationResult = info.CertificateChain.Check(info.ServerCommonName, 0)

			If res.Valid Then
				Return TlsSslCertificateAcceptance.Acceptable
			End If

			Dim status As CertificateValidationStatus = res.Status

			Dim values() As CertificateValidationStatus = CType(System.Enum.GetValues(GetType(CertificateValidationStatus)), CertificateValidationStatus())

			Dim addToTrustedListForm As Boolean = False
			Dim sb As New StringBuilder()
			For i As Integer = 0 To values.Length - 1
				If (status And values(i)) = 0 Then
					Continue For
				End If

				status = status Xor values(i)

				sb.AppendFormat("{0}" & vbCrLf, GetCertProblem(res, values(i), addToTrustedListForm))
			Next i

			Dim cert As X509Certificate = info.CertificateChain(0)

			' If the certificate is already saved then return Accept.
			If acceptedCertificates.Contains(Convert.ToBase64String(cert.GetHash())) Then
				Return TlsSslCertificateAcceptance.Acceptable
			End If

			Dim certForm As New CertValidator()
			certForm.Certificate = cert
			certForm.ShowAddToTrustedList = addToTrustedListForm

			certForm.ShowDialog()

			' Add certiticate of the issuer CA to the trusted list.
			If certForm.AddToTrustedList Then
				Dim trustedCaStore As New CertificateStore(CertificateStoreNameType.Root)
				Dim rootCertificate As X509Certificate = info.CertificateChain(info.CertificateChain.Count - 1)

				trustedCaStore.AddCertificate(rootCertificate)
			End If

			If certForm.Accepted Then
				' Save accepted certificate.
				acceptedCertificates.Add(Convert.ToBase64String(cert.GetHash()), cert)
				Return TlsSslCertificateAcceptance.Acceptable
			End If

			If (res.Status And CertificateValidationStatus.TimeNotValid) <> 0 Then
				Return TlsSslCertificateAcceptance.Expired
			End If
			If (res.Status And CertificateValidationStatus.Revoked) <> 0 Then
				Return TlsSslCertificateAcceptance.Revoked
			End If
			If (res.Status And (CertificateValidationStatus.UnknownCA Or CertificateValidationStatus.RootNotTrusted Or CertificateValidationStatus.IncompleteChain)) <> 0 Then
				Return TlsSslCertificateAcceptance.UnknownAuthority
			End If
			If (res.Status And (CertificateValidationStatus.Malformed Or CertificateValidationStatus.UnknownError)) <> 0 Then
				Return TlsSslCertificateAcceptance.Other
			End If

			Return TlsSslCertificateAcceptance.Unknow
		End Function
	End Class
End Namespace
