Using SSL and SslStream for peer to peer authentication?
I need to provide secure communication between various processes that are using TCP/IP sockets for communication. I want both authentication and encryption. Rather than re-invent the wheel I would really like to use SSL and the SslStream class and self-signed certificates. What I want to do is validate the remote process's certificate against a known copy in my local application. (There doesn't need to be a certificate authority because I intend for the certificates to be copied around manually).
To do this, I want the application to be able to automatically generate a new certifiate the first time it is run. In addition to makecert.exe, it looks like this link shows a way to automatically generate self-signed certificates, so that's a start.
I've looked at the AuthenticateAsServer and AuthenticateAsClient methods of SslStream. You can provide call-backs for verification, so it looks like it's possible. But now that I'm into the details of it, I really don't think it's possible to do this.
Am I going in the right direction? Is there a better alternative? Has anyone done anything like this before (basically peer-to-peer SSL rather than client-server)?
Solution 1:
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];
}
Solution 2:
you can look too this example Sample Asynchronous SslStream Client/Server Implementation http://blogs.msdn.com/joncole/archive/2007/06/13/sample-asynchronous-sslstream-client-server-implementation.aspx
if certificate is not produced correctly you can get exception The server mode SSL must use a certificate with the associated private key.
basic certificate example
makecert -sr LocalMachine -ss My -n CN=Test -sky exchange -sk 123456
or
as external file
makecert -sr LocalMachine -ss My -n CN=Test -sky exchange -sk 123456 c:\Test.cer
Certificate Creation Tool (Makecert.exe)
http://msdn.microsoft.com/en-us/library/bfsktky3%28VS.80%29.aspx