views:

2171

answers:

6

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)?

+1  A: 

What you're proposing sounds fine to me, except that it sounds like you're looking to wait until the callback is invoked in order to generate the certificate. I don't think that that will fly; AFAIK, you've got to provide a valid certificate when you invoke AuthenticateAsX.

However, these classes are overridable; so in theory, you could create a derived class which first checks to see if a certificate needs to be generated, generates it if need be, then invokes the parent AuthenticateAsX method.

Dan Breslau
Sorry, let me clarify. When I install the program, the first time it runs it would generate a new certificate. The server would call AuthenticateAsServer and pass this certificate, the client would call AuthenticateAsClient and pass it's certificate. But SslStream will probably throw an exception
Scott Whitlock
What exception would you expect, and why? Self-signed certificates should be acceptable to the SSL stack (try saying that five times fast :-)
Dan Breslau
Well, I'll give it a try and post the results.
Scott Whitlock
+5  A: 

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];
}
Scott Whitlock
A: 

Interesting ! And what about the client authentication part ? Have you done it ? Because I search on it but i failed to pass the authentication by the server : Server always reject me, because my client don't authenticate well. Does anybody know how to do that ?

Yes, I have it working. On both sides you have sslPolicyErrors, and I'm about the write the part that makes sure the only policy error is that it doesn't implicitly trust the root (since it's self-signed). I'll try to post what I have shortly.
Scott Whitlock
Ok, I posted how I wrote the client authentication. I hope that helps! Let me know if you have any other issues.
Scott Whitlock
A: 

Thanks, that's a nice example Scott. But I don't understand the security. Isn't the server supposed to keep a private key to itself while the clients gets a public key? To me it looks like CreateSelfSignCertificatePfx is returning both the private and public key.

Hi. In fact I'm trying to use a peer-to-peer rather than a client/server architecture. As such, both sides of the connection have a certificate with both a private and a public key. Both sides have to know the public key of the other side in order to trust each other. Each side has to know its own private key in order to respond to the challenge.
Scott Whitlock
A: 

When I generate certificate with this command

makecert -r -pe -n "CN=MachineName_SS" -ss my -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 MachineName_SS.cer

Handshaking succeeds, but when I create a self signed certificate as given here http://blogs.msdn.com/dcook/archive/2008/11/25/creating-a-self-signed-certificate-in-c.aspx

I get exception in AuthenticateAsServer(), is the above code not storing key in store,

Unhandled Exception: System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.

I want to do exactly what above makecert command does but in programmatic way.

Swapnil
I think you should move this into its own question.
Scott Whitlock
A: 

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

Mehmet Taskopru