C# - Implement Secure Web Socket - c#

I would like to ask if you know how to implement Secure Web Socket with .Net.
I've implemented ws:// and everything ok but I've no idea how to switch to wss://.
Thanks in advance.

You could try Fleck
Fleck is a WebSocket server implementation in C#
From their examples:
var server = new WebSocketServer("wss://0.0.0.0:8431");
server.Certificate = new X509Certificate2("MyCert.pfx");
server.Start(socket =>
{
//...use as normal
});

This question is very old but here's how i got my C# server accept an SSL connection from the client (js code running on Chrome / Firefox).
Assuming you already have a working and valid certificate (in my case the same certificate working to serve SSL on my Apache webserver), signed by a trusted CA (in my case, letsencrypt.org, which let you request a certificate for free), this is an excerpt from working code:
public static X509Certificate2 serverCertificate = null;
public Server(string ip_addr, int port)
{
serverCertificate = GetCertificateFromStore("CN=mydomain.com");
string resultsTrue = serverCertificate.ToString(true); // Debugging purposes
bool hasPrivateKey = serverCertificate.HasPrivateKey; // Debugging purposes (MUST return true)
Console.WriteLine("Certificate validation results: " + resultsTrue);
Console.WriteLine("Has private key? " + hasPrivateKey);
server = new TcpListener(IPAddress.Parse(ip_addr), port);
server.Start();
Console.WriteLine("Server has started on ip: " + ip_addr + ":"+port + " - Waiting for a connection...", Environment.NewLine);
}
public class ClientHandler
{
TcpClient client { get; set; }
//NetworkStream stream { get; set; } // Old plain non-secure tcp stream
SslStream stream { get; set; } // New secure tcp stream
....
public ClientHandler(TcpClient client, string room_id)
{
....
stream = new SslStream(client.GetStream(), false);
try
{
stream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: false);
// Set timeouts for the read and write to 5 seconds.
stream.ReadTimeout = 5000;
stream.WriteTimeout = 5000;
}
catch (Exception ex)
{
Console.WriteLine("Error during SSL authentication with the client:" + ex);
return;
}
}
}
The tricky part is that class X509Certificate2 needs to retrieve the certificate not from file but from your local keystore.
Also you need both the certificate file AND your private key for SSL to work.
I'm developing on Linux and Mono.Net but it should not change much on other platforms. The tools i needed were: openssl and certmgr (mono certificate manager).
To create the .pfx file containing the cert & the private key:
openssl pkcs12 -export -in yourcertfile.cer -inkey yourprivatekeyfile.pem -out finalfile.pfx
To add the file obtained to my local store:
certmgr -add -c -m Trust finalfile.pfx
Finally, you can edit your client side connection code to point to the same domain you're hosting your server (which should be the same domain reported in your certificate).
This:
var mySocket = new WebSocket("ws://127.0.0.1:5050");
Becomes:
var mySocket = new WebSocket("wss://yourdomain.com:5050");
Keep in mind that, once you've implemented SSL, you'll have to revise the whole networking code, since you're adding overhead to your TCP stream and you must take it into account when parsing the bytes and the bits to find and decode the headers.
This is where i'm stuck myself but beside that, SSL connection works great :)

If you use WebSocketSharp-NonPreRelease Package to develop the websocket you can simply add your certificate using below code
var wssv = new WebSocketServer (5963, true);
wssv.SslConfiguration.ServerCertificate =
new X509Certificate2 ("/path/to/cert.pfx", "password for cert.pfx");

Related

How to AuthenticateAsServer when Certificate is in HSM and privateKey is not embeded in Certificate and PrivateKey is not extractable from HSM

I want to ask a qusetion according to my code, My code is as bellow:
in AuthenticateAsServer I get "The server mode SSL must use a certificate with the associated private key" error cause privatekey is not in my certificate and also privatekey is not extractable from the HSM, would you please guid me what is the solution here?
static void ProcessClient(TcpClient client)
{
SslStream sslClientStream = new SslStream(client.GetStream(), true, AllowAnyServerCertificate, null, EncryptionPolicy.RequireEncryption);
try
{
X509Certificate2 _HsmserverCertificate = null;
string pkcs11LibraryPath = "C:\\Program Files (x86)\\nCipher\\nfast\\toolkits\\pkcs11\\cknfast-64.dll";
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, pkcs11LibraryPath, AppType.MultiThreaded))
{
ISlot slot = HelpersMethods.GetUsableSlot(pkcs11Library);
using (Net.Pkcs11Interop.HighLevelAPI.ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, #"1234");
var certificate = ReadCertificates(slot, session)[0];
_HsmserverCertificate = new X509Certificate2(certificate.CkaValue);
session.Logout();
}
}
sslClientStream.ReadTimeout = glb_intReciveTimeOut;
sslClientStream.WriteTimeout = glb_intSendTimeOut;
sslClientStream.AuthenticateAsServer(_HsmserverCertificate,
clientCertificateRequired: false,
SslProtocols.Tls12,
checkCertificateRevocation: true);
}
}
Unwritten rule in .NET world: If you want to use an instance X509Certificate2 class in SSL connection then you cannot create it manually but you need to acquire it from X509Store.
X509Store class provides access to all certificates propagated into windows certificate store. Take a look at your device documentation for more details on how to propagate your device certificates into windows certificate store. Sometimes it is also referred to as CAPI, CSP, CNG, KSP etc. If you are unfamiliar with those terms then your best bet is to contact device vendor support.

FTP Connection issue- using FluentFTP for port 990 -TLS

I am trying download file through FTPS connection with port 990 (TLS) using FluentFTP.
But the code is not able to establish connection and showing exception as "The remote certificate is invalid according to the validation procedure."
The FTP server is connecting properly when I use FileZilla FTP tool manually (showing as it is connected through ftps over TLS (Implicit)
FtpClient fclient = new FtpClient(hostname, username, password);
fclient.EncryptionMode = FtpEncryptionMode.Implicit;
fclient.SslProtocols = SslProtocols.Tls12; //Also tried with TLS1 and TLS
fclient.Port = 990;
fclient.Connect();
Try this (taken from ConnectFTPSCertificate.cs example of FluentFTP). The important part is the callback OnValidateCertificate.
public static async Task ConnectFTPSCertificateAsync() {
var token = new CancellationToken();
using (var conn = new FtpClient("127.0.0.1", "ftptest", "ftptest")) {
conn.EncryptionMode = FtpEncryptionMode.Explicit;
conn.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);
await conn.ConnectAsync(token);
}
}
private static void OnValidateCertificate(FtpClient control, FtpSslValidationEventArgs e) {
if (e.PolicyErrors == System.Net.Security.SslPolicyErrors.None) {
e.Accept = true;
}
else {
// add logic to test if certificate is valid here
// lookup the "Certificate" and "Chain" properties
e.Accept = false;
}
}
I experienced the same issue.
Pay attention that fluentFTP supports only external interfaces and not implicit
I also tried ftpWebRequest without success.
Try using winSCP.

SslStream.AuthenticateAsServer exception - The server mode SSL must use a certificate with the associated private key

I am developing a proxy server application similar to CCProxy. Its working fine for HTTP but not HTTPS. Its throwing exception when AuthenticateAsServer() method is called on SslStream object.
I also don't know whether I have supplied proper certificate, I don't know how to create a certificate. I just provided the certificate which came with the code that I downloaded online.
Here is the code:
private static void DoHttpProcessing(TcpClient client)
{
Stream clientStream = client.GetStream();
Stream outStream = clientStream;
SslStream sslStream = null;
StreamReader clientStreamReader = new StreamReader(clientStream);
CacheEntry cacheEntry = null;
MemoryStream cacheStream = null;
if (Server.DumpHeaders || Server.DumpPostData || Server.DumpResponseData)
{
Monitor.TryEnter(_outputLockObj, TimeSpan.FromMilliseconds(-1.0));
}
try
{
//read the first line HTTP command
String httpCmd = clientStreamReader.ReadLine();
if (String.IsNullOrEmpty(httpCmd))
{
clientStreamReader.Close();
clientStream.Close();
return;
}
//break up the line into three components
String[] splitBuffer = httpCmd.Split(spaceSplit, 3);
String method = splitBuffer[0];
String remoteUri = splitBuffer[1];
Version version = new Version(1, 0);
HttpWebRequest webReq;
HttpWebResponse response = null;
if (splitBuffer[0].ToUpper() == "CONNECT")
{
remoteUri = "https://" + splitBuffer[1];
while (!String.IsNullOrEmpty(clientStreamReader.ReadLine())) ;
StreamWriter connectStreamWriter = new StreamWriter(clientStream);
connectStreamWriter.WriteLine("HTTP/1.0 200 Connection established");
connectStreamWriter.WriteLine(String.Format("Timestamp: {0}", DateTime.Now.ToString()));
connectStreamWriter.WriteLine("Proxy-agent: matt-dot-net");
connectStreamWriter.WriteLine();
connectStreamWriter.Flush();
sslStream = new SslStream(clientStream, false);
try
{
// HERE I RECEIVE EXCEPTION
sslStream.AuthenticateAsServer(_certificate, false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, true);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
sslStream.Close();
clientStreamReader.Close();
connectStreamWriter.Close();
clientStream.Close();
return;
}//further code goes here...
Also, instead of sslStream.AuthenticateAsServer, if I use ssStream.AuthenticateAsClient method I get AuthenticationException with message "A call to SSPI failed, see inner exception." and InnerException gives message as "The message received was unexpected or badly formatted"
When I am using sslstream.AuthenticateAsServer() method, I need to create certificate for each new HTTPS host and pass it with this method. If I provide the self signed certificate, the request succeeds. But problem is, for how many new HTTPS requests will I keep creating certificates manually and assign it to AuthenticateAsServer()?
For the serverside certificate, most certificates correspond to a FQDN (so server1.localhost.local), although there can be wildcard certs (*.localhost.local). When you use AuthenticateAsClient method, that could be one of two things, 1) The Certificate doesnt have the Extended Key Usage for Client Authentication or 2) you didnt pass the right password for it to read the cert/private key. To get past both these hurdles rather quickly i would suggest creating an OpenSSL CA and then generating a CA and Server Cert. There is tons of documentation on how to do this and should take 30 minutes tops for someone who has never created one before....(Also i would suggest exporting the cert into pkcs12 extension for the CA to be chained in with the Server Cert).

How do I identify my server name for server authentication by client in c#

I have recently been trying to make a SSL encrypted Server/Client in C#.
I have followed this tutorial on MSDN, however, it required a certificate to be created for the server and client usage using makecert.exe so I found an example and it created the certificate fine:
makecert -sr LocalMachine -ss My -n "CN=Test" -sky exchange -sk 123456 c:/Test.cer
but now the problem is the server starts and waits for clients, when the client connects it uses the machine name which as far as I can gather is my IP in this case:
127.0.0.1
, and then it requires the servers name which must match the servers name on the certificate (Test.cer). I have tried multiple combinations (such as "Test" "LocalMachine","127.0.0.1" but cant seem to get the clients given server name to match thus allowing the connection. The error I get is:
Certificate error: RemoteCertificateNameMismatch, RemoteCertificateChainErrors
Exception: the remote certificate is invalid according to the validation procedure
here is the code I'm using it differs from the MSDN example only in the fact that I assign the certificate path for the server in the app and the machine name and server name of the client too:
SslTcpServer.cs
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace Examples.System.Net
{
public sealed class SslTcpServer
{
static X509Certificate serverCertificate = null;
// The certificate parameter specifies the name of the file
// containing the machine certificate.
public static void RunServer(string certificate)
{
serverCertificate = X509Certificate.CreateFromCertFile(certificate);
// Create a TCP/IP (IPv4) socket and listen for incoming connections.
TcpListener listener = new TcpListener(IPAddress.Any, 8080);
listener.Start();
while (true)
{
Console.WriteLine("Waiting for a client to connect...");
// Application blocks while waiting for an incoming connection.
// Type CNTL-C to terminate the server.
TcpClient client = listener.AcceptTcpClient();
ProcessClient(client);
}
}
static void ProcessClient(TcpClient client)
{
// A client has connected. Create the
// SslStream using the client's network stream.
SslStream sslStream = new SslStream(
client.GetStream(), false);
// Authenticate the server but don't require the client to authenticate.
try
{
sslStream.AuthenticateAsServer(serverCertificate,
false, SslProtocols.Tls, true);
// Display the properties and settings for the authenticated stream.
DisplaySecurityLevel(sslStream);
DisplaySecurityServices(sslStream);
DisplayCertificateInformation(sslStream);
DisplayStreamProperties(sslStream);
// Set timeouts for the read and write to 5 seconds.
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
// Read a message from the client.
Console.WriteLine("Waiting for client message...");
string messageData = ReadMessage(sslStream);
Console.WriteLine("Received: {0}", messageData);
// Write a message to the client.
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
Console.WriteLine("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally
{
// The client stream will be closed with the sslStream
// because we specified this behavior when creating
// the sslStream.
sslStream.Close();
client.Close();
}
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
// Read the client's test message.
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
static void DisplaySecurityLevel(SslStream stream)
{
Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
Console.WriteLine("Protocol: {0}", stream.SslProtocol);
}
static void DisplaySecurityServices(SslStream stream)
{
Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
Console.WriteLine("IsSigned: {0}", stream.IsSigned);
Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
}
static void DisplayStreamProperties(SslStream stream)
{
Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
}
static void DisplayCertificateInformation(SslStream stream)
{
Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);
X509Certificate localCertificate = stream.LocalCertificate;
if (stream.LocalCertificate != null)
{
Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
localCertificate.Subject,
localCertificate.GetEffectiveDateString(),
localCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Local certificate is null.");
}
// Display the properties of the client's certificate.
X509Certificate remoteCertificate = stream.RemoteCertificate;
if (stream.RemoteCertificate != null)
{
Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
remoteCertificate.Subject,
remoteCertificate.GetEffectiveDateString(),
remoteCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Remote certificate is null.");
}
}
public static void Main(string[] args)
{
string certificate = "c:/Test.cer";
SslTcpServer.RunServer(certificate);
}
}
}
SslTcpClient.cs
using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace Examples.System.Net
{
public class SslTcpClient
{
private static Hashtable certificateErrors = new Hashtable();
// The following method is invoked by the RemoteCertificateValidationDelegate.
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
public static void RunClient(string machineName, string serverName)
{
// Create a TCP/IP client socket.
// machineName is the host running the server application.
TcpClient client = new TcpClient(machineName, 8080);
Console.WriteLine("Client connected.");
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
// The server name must match the name on the server certificate.
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
return;
}
// Encode a test message into a byte array.
// Signal the end of the message using the "<EOF>".
byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
// Send hello message to the server.
sslStream.Write(messsage);
sslStream.Flush();
// Read message from the server.
string serverMessage = ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
// Close the client connection.
client.Close();
Console.WriteLine("Client closed.");
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
public static void Main(string[] args)
{
string serverCertificateName = null;
string machineName = null;
/*
// User can specify the machine name and server name.
// Server name must match the name on the server's certificate.
machineName = args[0];
if (args.Length < 2)
{
serverCertificateName = machineName;
}
else
{
serverCertificateName = args[1];
}*/
machineName = "127.0.0.1";
serverCertificateName = "David-PC";// tried Test, LocalMachine and 127.0.0.1
SslTcpClient.RunClient(machineName, serverCertificateName);
Console.ReadKey();
}
}
}
EDIT:
The server accepts the clients connection and everything but it times out while waiting for the client to send a message. (The client wont authenticate with server due to the server name in the certificate being different from the one I supplied in the client) well thats my thoughts on it just to clarify
UPDATE:
According to an answer I have changed the certficiate maker to:
makecert -sr LocalMachine -ss My -n "CN=localhost" -sky exchange -sk 123456 c:/Test.cer
and in my client I have:
machineName = "127.0.0.1";
serverCertificateName = "localhost";// tried Test, LocalMachine and 127.0.0.1
SslTcpClient.RunClient(machineName, serverCertificateName);
now I get the exception:
RemoteCertificateChainErrors
Exception: the remote certificate is invalid according to the validation procedure
which is occuring here:
// The server name must match the name on the server certificate.
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.Message);
if (e.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection. "+ e.Message);
client.Close();
return;
}
The answer can be found at SslStream.AuthenticateAsClient Method Remarks section:
The value specified for targetHost must match the name on the server's certificate.
If you use for the server a certificate whose subject is "CN=localhost", you must call AuthenticateAsClient with "localhost" as targetHost parameter to successfully authenticate it on the client side. If you would use "CN=David-PC" as certificate subject than you must call AuthenticateAsClient with "David-PC" as targetHost. SslStream checks the server identity by matching the server name that you intend to connect (and which you pass to AuthenticateAsClient) with the subject in the certificate received from the server. The practice is that the machine name that runs the server matches the name of the certificate's subject, and in the client you pass the same hostname to AuthenticateAsClient as you have used for opening the connection (with TcpClient in this case).
However there are other conditions to successfully establish SSL connection between servers and clients: the certificate passed to AuthenticateAsServer must have a private key, it must be trusted on the client machine and must not have any key usage restrictions related to usage for establishing SSL sessions.
Now related to your code sample, your problem is related to the generation and usage of the certificate.
You are not providing an issuer for your certificate and in this way it can't be trusted - this is the cause of the RemoteCertificateChainErrors Exception. I suggest to create a self signed certificate for development purposes specifying the -r option of makecert.
To be trusted a certificate must either be self-signed and placed in a trusted location in the Windows Certificate Store or must be linked with a chain of signatures to an already trusted Certificate Authority. So instead of the -ss My option which will place the certificate in the Personal store use -ss root that will place it in the Trusted Root Certification Authorities and it will be trusted on your machine (from the code I assume that your client is running on the same machine with the server and also the certificate is generated on it).
If you specify a output file to makecert it will export the certificate as .cer but this format contains only the public key, not the private key that is needed by the server to establish a SSL connection. The easiest way is to read the certificate from the Windows Certificate store in the server code. (You can also export it from the store in another format that allows storing the private key as described here Export a certificate with the private key and read that file in the server code).
You can find details about the makecert options used here Certificate Creation Tool (Makecert.exe)
In conclusion your code needs the following changes to run (tested with your latest code updates):
Use the following command to generate the certificate:
makecert -sr LocalMachine -ss root -r -n "CN=localhost" -sky exchange
-sk 123456
Read the certificate from Windows Certificate Store instead of a file (for the simplicity of this example), so replace
serverCertificate = X509Certificate.CreateFromCertFile(certificate);
in the server code with:
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, "CN=localhost", false);
store.Close();
if (certificates.Count == 0)
{
Console.WriteLine("Server certificate not found...");
return;
}
else
{
serverCertificate = certificates[0];
}
Please remember to replace "CN=localhost" with the subject of the certificate that you intend to use if you change the code later (in this situation should be the same value as the -n option passed to makecert). Also consider to use the machine name that runs the server instead of localhost in the server certificate's subject.
A server certificate's CN must be exactly the same as the server's domain name. I suppose, in your case the common name must be "localhost" (w/o quotes).
Important: for sure, as you might have read in other answers, never use CN="localhost" in production.
First, do not create a certificate with the subject "CN=localhost" or equivalent. It is never going to be used in production so don't do it. Always issue it to your computer's hostname, e.g. CN="mycomputer", and use the host name when connecting to it rather than localhost. You can specify multiple names using the "subject alternate name" extension but makecert does not appear to support it.
Second, when issuing a server SSL certificate, you need to add the "server authentication" OID to the enhanced key usage (EKU) extension of the certificate. Add the -eku 1.3.6.1.5.5.7.3.1 parameter to makecert in your example. If you want to do client certificate authentication, use the "client authentication" OID of 1.3.6.1.5.5.7.3.2.
Lastly, the default certificate created by makecert uses MD5 as its hashing algorithm. MD5 is considered insecure and, although it will not affect your testing, get into the habit of using SHA1. Add -a sha1 to the makecert parameters above to force SHA1. The default key size should also be increased from 1024-bits to 2048-bits but you get the idea.
To get this to work with WCF it is neccessary to first create a self-signed root authority certificate and then use it to create the certificate for localhost.
I'm thinking the same might apply to your project as well, please have a look at this article How to: Create Temporary Certificates for Use During Development for details.
Have you tried:?
Create the certificate for a full domain name like example.net (it's good to use example.net, example.com or example.org for anything that's deliberately not a real name) or the name that will be used in live use if that's a single site and you know what it will be.
Update your hosts file so that it will use 127.0.0.1 for that name.
In regard to your update:
One of the SslStream constructors allows you to provide a RemoteCertificateValidationCallback delegate. You should be able to put a breakpoint in the method that you provide to see what the actual error you're getting is. Check the SslPolicyErrors value sent in.

Using certificate file to connect to webservice over SSL

I am developing windows service in C# which invokes webservice methods. I must use SSL to connect to webservice. I have recieved from publisher p12 file with certificate. The file is password protected. To use Import method to use this certificate. Everything is working fine, but I do not like this method - I have password harcoded in my app. When publisher changes certificate I must rewrite code(changing the password to new one). Is there any way not to harcode password to .p12 file or use other option(.cer file)?
What you could do is something like this:
Install the SSL certificate into your local machine certificate store (using the Microsoft Management Console "MMC")
Extract the certificates thumbprint (e.g. "748681ca3646ccc7c4facb7360a0e3baa0894cb5")
Use a function which fetches you the certificate from the local certificate store for the given thumbprint.
Provide the SSL certificate when calling your web service.
private static X509Certificate2 GetCertificateByThumbprint(string certificateThumbPrint, StoreLocation certificateStoreLocation) {
X509Certificate2 certificate = null;
X509Store certificateStore = new X509Store(certificateStoreLocation);
certificateStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certificateStore.Certificates;
foreach (X509Certificate2 cert in certCollection)
{
if (cert.Thumbprint != null && cert.Thumbprint.Equals(certificateThumbPrint, StringComparison.OrdinalIgnoreCase))
{
certificate = cert;
break;
}
}
if (certificate == null)
{
Log.ErrorFormat(CultureInfo.InvariantCulture, "Certificate with thumbprint {0} not found", certificateThumbPrint);
}
return certificate;
}
public string GetServiceResponse() {
string WebSvcEndpointConfigurationName = "WebServiceEndpoint";
Uri webSvcEndpointAddress = new Uri("http://www.example.com/YourWebService.svc");
string webSvcCertificateThumbPrint = "748681ca3646ccc7c4facb7360a0e3baa0894cb5";
string webSvcResponse = null;
SomeWebServiceClient webServiceClient = null;
try
{
webServiceClient = new SomeWebServiceClient(WebSvcEndpointConfigurationName, new EndpointAddress(webSvcEndpointAddress));
webServiceClient.ClientCredentials.ClientCertificate.Certificate = GetCertificateByThumbprint(webSvcCertificateThumbPrint, StoreLocation.LocalMachine);
webSvcResponse = webServiceClient.GetServiceResponse();
}
catch (Exception ex)
{
}
finally
{
if (webServiceClient != null)
{
webServiceClient.Close();
}
}
return webSvcResponse;
}
PKCS#12 file is provided to you as it is a natural way to transport certificates together with private keys. You can use one of the following:
convert it to format you like and store the way you like
convert it to passwordless PFX
import it to computer's certificate storage and use it this way
But all those methods (together with keeping a hardcoded password) provide no real protection to the private key and thus are not usable if you distribute the application to outside of your organization.

Categories