Using Ssl and Sslstream for Peer to Peer Authentication

Using SSL and SslStream for peer to peer authentication?

Step 1: Generating a self-signed certificate:

  • I downloaded the Certificate.cs class posted by Doug Cook
  • I used this code to generate a .pfx certificate file:

    byte[] c = Certificate.CreateSelfSignCertificatePfx(
    "CN=yourhostname.com", //host name
    DateTime.Parse("2000-01-01"), //not valid before
    DateTime.Parse("2010-01-01"), //not valid after
    "mypassword"); //password to encrypt key file

    using (BinaryWriter binWriter = new BinaryWriter(
    File.Open(@"testcert.pfx", FileMode.Create)))
    {
    binWriter.Write(c);
    }

Step 2: Loading the certificate

    X509Certificate cert = new X509Certificate2(
@"testcert.pfx",
"mypassword");

Step 3: Putting it together

  • I based it on this very simple SslStream example
  • You will get a compile time error about the SslProtocolType enumeration. Just change that from SslProtocolType.Default to SslProtocols.Default
  • There were 3 warnings about deprecated functions. I replaced them all with the suggested replacements.
  • I replaced this line in the Server Program.cs file with the line from Step 2:

    X509Certificate cert = getServerCert();

  • In the Client Program.cs file, make sure you set serverName = yourhostname.com (and that it matches the name in the certificate)

  • In the Client Program.cs, the CertificateValidationCallback function fails because sslPolicyErrors contains a RemoteCertificateChainErrors. If you dig a little deeper, this is because the issuing authority that signed the certificate is not a trusted root.
  • I don`t want to get into having the user import certificates into the root store, etc., so I made a special case for this, and I check that certificate.GetPublicKeyString() is equal to the public key that I have on file for that server. If it matches, I return True from that function. That seems to work.

Step 4: Client Authentication

Here's how my client authenticates (it's a little different than the server):

TcpClient client = new TcpClient();
client.Connect(hostName, port);

SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(CertificateValidationCallback),
new LocalCertificateSelectionCallback(CertificateSelectionCallback));

bool authenticationPassed = true;
try
{
string serverName = System.Environment.MachineName;

X509Certificate cert = GetServerCert(SERVER_CERT_FILENAME, SERVER_CERT_PASSWORD);
X509CertificateCollection certs = new X509CertificateCollection();
certs.Add(cert);

sslStream.AuthenticateAsClient(
serverName,
certs,
SslProtocols.Default,
false); // check cert revokation
}
catch (AuthenticationException)
{
authenticationPassed = false;
}
if (authenticationPassed)
{
//do stuff
}

The CertificateValidationCallback is the same as in the server case, but note how AuthenticateAsClient takes a collection of certificates, not just one certificate. So, you have to add a LocalCertificateSelectionCallback, like this (in this case, I only have one client cert so I just return the first one in the collection):

static X509Certificate CertificateSelectionCallback(object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
return localCertificates[0];
}

SslStream and Authentication

EDIT: the MSDN has a complete working example at the bottom of this page: https://msdn.microsoft.com/en-us/library/system.net.security.sslstream?f=255&MSPPError=-2147217396 - so you should really start experimenting there because that example has it all.

Original answer:

I must preface this answer that "client authentication not required" is the case for most of the SSL implementations. Client authentication is rare: you're likely to see it in VPN apps, the banking industry and other secure apps. So it would be wise when you are experimenting with SslStream() to start without client authentication.

When you browse to an HTTPS website, you don't authenticate your browser with a client cert, instead you just want to confirm the server name you are connecting to matches up to the CNAME found in the cert and that the server cert is signed by a CA that your machine trusts - there's more to it, but essentially that's what it boils down to.

So, having said that, let me answer your questions:

1) SslStream.AuthenticateAsServer(...) is done ONLY on server side with the server 509 certificate. On the client side, you must call SslStream.AuthenticateAsClient(serverName) with server name being the CNAME (common name) of your certificate (example: "domain.com")

2) SslStream must be created for both the client and the server. You create it simply by "wrapping" a TcpClient NetworkStream around it (for example, but there are other methods)

Example for the server:

// assuming an 509 certificate has been loaded before in an init method of some sort
X509Certificate serverCertificate = X509Certificate2.CreateFromCertFile("c:\\mycert.cer"); // for illustration only, don't do it like this in production
...

// assuming a TcpClient tcpClient was accepted somewhere above this code
slStream sslStream = new SslStream(tcpClient.GetStream(), false);
sslStream.AuthenticateAsServer(
serverCertificate,
false,
SslProtocols.Tls,
true);

3) No. The communication is encrypted on both ends. So both sides must use SslStream. Using receive() and send() on the client would yield binary encrypted data.

4) No. The client passes a callback method to the SslStream creation in order to validate the certificate received by the server.

Example:

// assuming a TcpClient tcpClient was connected to the server somewhere above this code
SslStream sslStream = new SslStream(
tcpClient.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
sslStream.AuthenticateAsClient(serverName); // serverName: "domain.com" for example

then somewhere else in your code:

public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None) {
return true;
}

Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

// refuse connection
return false;
}

Peer-peer mutal SSL authentication in .Net using socket communication

Hi guys if you are still facing the same issues as that i have posted please follow the below link.
Our team worked on it and we found the below solution, hope it helps.

SSL mutual authentication Certificate issue

How to tell C# SslStream AuthenticateAsServer to send a Certificate Trust List (CTL)

Finally figured this out:

create a DWORD Value under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
called "SendTrustedIssuerList" and set to "1"

Authenticating a SSL server against a pre-obtained copy of its self-signed certificate

The first solution is simple but still has vulnerabilities. That is to just check that the key fingerprint matches. But, any middle man can establish an intermediary connection and create his own key exchange and then craft yours to match his, sharing encrypting keys at that point.

The fundamental problem is that you don't have a proper infrastructure. It's not that you're using self signed certificates per se, but it's that you're using self signed certificates from the same "CA". You need to create a root certificate for your certification authority (yourself) and then from that key you need to approve a CSR for your piece of software to use. This will mean that all remote machines need to trust your root certificate and install it as a certificate authority in their machine. By doing this you would avoid the issue of having the certificate used by the application being compromised mid-stream or anything like that. But this only applies if you can get that root CA certificate out to the users in a guaranteed tamper-proof fashion.

A different but far more drastic solution to your problem is to essentially ditch SSL or do out of band authentication for that certificate's validity. The client can verify the certificate because the server is able to decrypt the client's messages and make appropriate responses. A challenge response scheme like this can mathematically/cryptographically prove that the server has the expected certificate or at least that the party has the corresponding private key for it:

Client generates a 256 bit random nonce (RNGCryptoServiceProvider), K_C
Server generates a 256 bit random nonce, K_S
Client encrypts K_C with server's assumed public key, now K_B
Server digitally signs K_S, K_A
Server transmits K_A to client
Client transmits K_B to server
Server decrypts K_B, now N_S
Client verifies K_A using server's assumed public key, if invalid disconnect
Client computes K_A XOR K_C, now Z_C
Server computes N_S XOR K_A, now Z_S
Server transmits Z_S to client
Client verifies Z_S matches Z_C
Client transmits Z_C to server, encrypted using the server's public key
Server decrypts Z_C and verifies that it matches Z_S

If everything checks out, the challenge is complete and you can guarantee the server is in possession of the private key for the public key you have.
You also can't just shortcut this and transmit a digitally signed copy of the server's public key to the client becaue then any middle man can create two sessions on either side
This scheme prevents two sessions from being established by a man in the middle because the client is in possession of the public key already and challenges the validity directly by encrypting their nonce with the server's public key.
The best attack on this is a man in the middle observer that can't tamper with the data without exploiting a weakness in the asymmetric algorithm being used.

Another benefit to this scheme is that you can just avoid SSL all together at this point, since you have authenticated independently. Now, you can actually use Z_S == Z_C to your advantage and consider Z_S or Z_C (they're identical) as your agreed upon symmetric key for further communication.

I wrote a paper on a scheme similar to this a while ago and it was published a few places. You can probably find it by the name of "Challenging Authentication-Agreement Protocol (CAAP)", dated 2007 or revised in 2010-ish under my name. There are code examples.

SslStream authentication failure

The following code will avoid the Windows certificate stores and validate the chain.

I see no reason to add the CA certificate needed to verify a chain to the hundreds in the certificate store already. That means Windows will try to verify the chain with "hundreds + 1" certificates rather than the one certificate truly required.

I have not figured out how to use this chain (chain2 below) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work". And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback. I think these are architectural defects in .Net, but I might be missing something obvious.

The name of the function does not matter. Below, VerifyServerCertificate is the same callback as RemoteCertificateValidationCallback in SslStream class. You can also use it for the ServerCertificateValidationCallback in ServicePointManager.

static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);

X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);

// Check all properties
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

// Build the chain
chain2.Build(new X509Certificate2(certificate));

// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return true;

// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);

return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}

return false;
}


Related Topics



Leave a reply



Submit