Last active
May 27, 2017 00:41
-
-
Save pacodelacruz/366e841890b68151bb1d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Configuration; | |
using System.IdentityModel.Tokens; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Security; | |
using System.ServiceModel; | |
using System.ServiceModel.Security; | |
using System.Security.Cryptography; | |
using System.Security.Cryptography.X509Certificates; | |
namespace MutualAuthClient | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
try | |
{ | |
Console.WriteLine("Starting..."); | |
// Set the ServerCertificateValidationCallback property to a | |
// custom method. | |
ServicePointManager.ServerCertificateValidationCallback += | |
CustomServiceCertificateValidation; | |
// We will call a service which expects a string and echoes it | |
// as a response. | |
var client = new EchoService.EchoServiceClient | |
("BasicHttpBinding_IEchoService"); | |
// Load private key from PFX file. | |
// Reading from a PFX file requires specifying the password. | |
// You might want to consider adding encryption here. | |
Console.WriteLine("Loading Client Certificate (Private Key) from File: " | |
+ ConfigurationManager.AppSettings["ClientPFX"]); | |
client.ClientCredentials.ClientCertificate.Certificate = | |
new X509Certificate2( | |
ConfigurationManager.AppSettings["ClientPFX"], | |
ConfigurationManager.AppSettings["ClientPFXPassword"], | |
X509KeyStorageFlags.MachineKeySet); | |
// We are using a custom method for the Server Certificate Validation | |
client.ClientCredentials.ServiceCertificate.Authentication. | |
CertificateValidationMode = | |
X509CertificateValidationMode.None; | |
Console.WriteLine(); | |
Console.WriteLine(String.Format("About to call client.Echo")); | |
string response = client.Echo("Test"); | |
Console.WriteLine(); | |
Console.WriteLine(String.Format("client.Echo Response: '{0}'", response)); | |
Console.ReadLine(); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine( | |
String.Format("Exception occurred{0}Message:{1}{2}Inner Exception: {3}" | |
, Environment.NewLine, ex.Message, Environment.NewLine, | |
ex.InnerException)); | |
} | |
} | |
private static bool CustomServiceCertificateValidation( | |
object sender, X509Certificate cert, X509Chain chain, | |
SslPolicyErrors error) | |
{ | |
Console.WriteLine(); | |
Console.WriteLine("CustomServiceCertificateValidation has started"); | |
// Load the authorised and expected service certificate (public key) | |
// from file. | |
Console.WriteLine("Loading Service Certificate (Public Key) from File: " | |
+ ConfigurationManager.AppSettings["ServicePublicKey"]); | |
X509Certificate2 authorisedServiceCertificate = new X509Certificate2 | |
(ConfigurationManager.AppSettings["ServicePublicKey"]); | |
// Load the trusted CA (public key) from file. | |
Console.WriteLine("Loading the Trusted CA (Public Key) from File: " | |
+ ConfigurationManager.AppSettings["TrustedCAPublicKey"]); | |
X509Certificate2 trustedCertificateAuthority = new X509Certificate2 | |
(ConfigurationManager.AppSettings["TrustedCAPublicKey"]); | |
// Load the received certificate from the service (input parameter) as | |
// an X509Certificate2 | |
X509Certificate2 serviceCert = new X509Certificate2(cert); | |
// Compare the received service certificate against the configured | |
// authorised service certificate. | |
if (!authorisedServiceCertificate.Equals(serviceCert)) | |
{ | |
// If they are not the same, throw an exception. | |
throw new SecurityTokenValidationException(String.Format( | |
"Service certificate '{0}' does not match that authorised '{1}'" | |
, serviceCert.Thumbprint, authorisedServiceCertificate.Thumbprint)); | |
} | |
else | |
{ | |
Console.WriteLine(String.Format( | |
"Service certificate '{0}' matches the authorised certificate '{1}'." | |
, serviceCert.Thumbprint, authorisedServiceCertificate.Thumbprint)); | |
} | |
// Create a new X509Chain to validate the received service certificate using | |
// the trusted CA | |
X509Chain chainToValidate = new X509Chain(); | |
// When working with Self-Signed certificates, | |
// there is no need to check revocation. | |
// You might want to change this when working with | |
// a properly signed certificate. | |
chainToValidate.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; | |
chainToValidate.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; | |
chainToValidate.ChainPolicy.VerificationFlags = | |
X509VerificationFlags.AllowUnknownCertificateAuthority; | |
chainToValidate.ChainPolicy.VerificationTime = DateTime.Now; | |
chainToValidate.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0); | |
// Add the configured authorised Certificate Authority to the chain. | |
chainToValidate.ChainPolicy.ExtraStore.Add(trustedCertificateAuthority); | |
// Validate the received service certificate using the trusted CA | |
bool isChainValid = chainToValidate.Build(serviceCert); | |
if (!isChainValid) | |
{ | |
// If the certificate chain is not valid, get all returned errors. | |
string[] errors = chainToValidate.ChainStatus | |
.Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), | |
x.Status)) | |
.ToArray(); | |
string serviceCertChainErrors = "No detailed errors are available."; | |
if (errors != null && errors.Length > 0) | |
serviceCertChainErrors = String.Join(", ", errors); | |
throw new SecurityTokenValidationException(String.Format( | |
"The chain of service certificate '{0}' is not valid. Errors: {1}", | |
serviceCert.Thumbprint, serviceCertChainErrors)); | |
} | |
// Validate that the Service Certificate Chain Root matches the Trusted CA. | |
if (!chainToValidate.ChainElements | |
.Cast<X509ChainElement>() | |
.Any(x => x.Certificate.Thumbprint == | |
trustedCertificateAuthority.Thumbprint)) | |
{ | |
throw new SecurityTokenValidationException(String.Format( | |
"The chain of Service Certificate '{0}' is not valid. " + | |
" Service Certificate Authority Thumbprint does not match " + | |
"Trusted CA's Thumbprint '{1}'", | |
serviceCert.Thumbprint, trustedCertificateAuthority.Thumbprint)); | |
} | |
else | |
{ | |
Console.WriteLine(String.Format( | |
"Service Certificate Authority '{0}' matches the Trusted CA's '{1}'", | |
serviceCert.IssuerName.Name, | |
trustedCertificateAuthority.SubjectName.Name)); | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For a detailed explanation,
http://blog.kloud.com.au/2015/11/23/implementing-a-wcf-client-with-certificate-based-mutual-authentication-without-using-windows-certificate-store/