Stuck on AuthenticateAsServer method in mitm https proxy - c#

I'm trying to write a simple https mitm proxy, and the problem arises when I handle the request:
public async Task Run(NetworkStream client, NetworkStream host) {
try {
//getting the cert
var certificate = new X509Certificate(#"[PATH_TO_CERT]", "[PASSWORD]");
//creating client's Ssl Stream
var clientStream = new SslStream(client, false);
//there the program freezes
clientStream.AuthenticateAsServer(certificate, false, SslProtocols.Default, false);
//creating server's Ssl Stream
var serverSslStream = new SslStream(host, false, SslValidationCallback, null);
serverSslStream.AuthenticateAsClient("[HOSTNAME]");
//...
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw;
}
}
After the request from client is sent, the program freezes at this line
clientStream.AuthenticateAsServer(certificate, false, SslProtocols.Default, false);
and it doesn't throw any exceptions. At first I thought that the problem is in the client's stream, so I tried to pass it's TcpClient as a method parameter, but nothing changed.
My self-signed certificate and .pfx file has been created like that:
makecert -n CN=*.[HOSTNAME].com -ic MyCA.cer -iv MyCA.pvk -a sha1 -sky exchange -pe -sr currentuser -ss my SslServer.cer
makecert.exe -pe -n "CN=*.[HOSTNAME].com" -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -ic CA.cer -iv CA.pvk -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv server.pvk server.cer
pvk2pfx -pvk MyCA.pvk -pi [PASSWORD] -spc MyCA.cer -pfx MyPFX.pfx -f
So I thought the problem is in this line
var certificate = new X509Certificate(#"[path to the cert]", "[password]");
I replaced the cer path to the pfx path and I even downloaded the original crt file new X509Certificate(#"[path to the original cert]");, but none of this worked.
I don't know where the problem is, I tried different clients, the result is the same.
My Visual Studio version is 15.7.27703.2018 and .Net is 4.7.1.
Any tips, suggestions or links that could help me?

Turned out that i needed to use it with await.
The final code looks like this:
//getting the cert
var certificate = new X509Certificate2(#"[PATH_TO_CERT]", "[PASSWORD]");
//creating client's Ssl Stream
var clientStream = new SslStream(client, false);
await clientStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3, false);

Related

C# .NET Core 3.1 The SSL connection could not be established

I use an OData service with an C# ASP.NET Core 3.1 service inside a docker container from SAP with a customer self-signed certificate.
In the meantime I have tried a thousand things, but the error persists.
System.InvalidOperationException: An error occurred while processing
this request. mdt2oowvsap_1 | --->
System.Data.Services.Client.DataServiceTransportException: The SSL
connection could not be established, see inner exception. The remote
certificate is invalid according to the validation procedure.
Even the unsafe solutions like using HttpClientHandler.ServerCertificateCustomValidationCallback with direct return true did not change anything.
public MyService()
{
:
handler = new HttpClientHandler()
{
Credentials = new NetworkCredential(Username, Password, Domain),
PreAuthenticate = true,
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
logger.LogDebug($"No SSL policy errors!");
return true; //Is valid
}
logger.LogDebug($"Get certificate hash: {cert.GetCertHashString()}");
// From: https://stackoverflow.com/questions/2675133/c-sharp-ignore-certificate-errors
if (!String.IsNullOrEmpty(certificateHash) && cert.GetCertHashString().Equals(certificateHash))
{
// Get hash string via: openssl s_client -connect <host>:<port> < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin
// see: https://stackoverflow.com/questions/5164804/get-certificate-fingerprint-of-https-server-from-command-line
logger.LogDebug($"Using certificate hash: {certificateHash}");
return true;
}
return false;
},
UseCookies = true,
CookieContainer = cookieContainer
};
String[] files = Directory.GetFiles("./certs/", "*.*", SearchOption.TopDirectoryOnly);
logger.LogInformation($"Found {files.Length} certificate files");
// see: https://stackoverflow.com/questions/40014047/add-client-certificate-to-net-core-httpclient
foreach (string cfile in files)
{
try
{
logger.LogDebug($"Try adding {cfile} as trusted client certificate...");
handler.ClientCertificates.Add(new X509Certificate2(Path.GetFullPath(cfile)));
}catch(Exception e)
{
logger.LogInformation($"Exception while adding certificate file {cfile} to 'ClientCertificates'");
logger.LogError(e.ToString());
}
}
httpClient = new HttpClient(handler);
:
}
The last attempt was to download the certificate and give it to the HttpClientHandler using ClientCertificates.Add. Without success.
Using curl, passing this certificate file works.
$> curl --location --request GET 'https://customer.de:1234/sap/opu/odata/something/useful_srv' \
--header 'Authorization: Basic ABCDEFGHIJKLMNOPQRST='
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
:
$> echo -n | openssl s_client -connect customer.de:1234 -servername customer.de | \
openssl x509 > /full/path/to/customer.cert
depth=0 CN = customer.de
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = customer.de
verify return:1
DONE
$>
$> curl --location --cacert '/full/path/to/customer.cert' --request GET \
'https://customer.de:1234/sap/opu/odata/something/useful_srv' \
--header 'Authorization: Basic ABCDEFGHIJKLMNOPQRST='
<?xml version="1.0" encoding="utf-8"?><app:service xml:lang="de" xml:base="https:blablabla.../></app:service>
$> _
Does anyone have another idea?
Solutions viewed (incomplete):
c-sharp-ignore-certificate-errors
add-client-certificate-to-net-core-httpclient
allowing-untrusted-ssl-certificates-with-httpclient
how-do-i-add-a-ca-root-certificate-inside-a-docker-image
the-ssl-connection-could-not-be-established
c-sharp-core-3-1-getting-error-message-the-ssl-connection-could-not-be-establ
the-ssl-connection-could-not-be-established-between-webapp-and-webapi-asp-core
how-to-fix-the-ssl-connection-could-not-be-established-see-inner-exception-w
Thanks in advance.
The solution to the problem was to make the "self-signed" certificate available to the Docker container or the operating system it contains (Ubuntu) and provide it with
cp certs/customer.cert /usr/local/share/ca-certificates/customer.crt &&
update-ca-certificates
Now the communication works over SSL.
I would like to add to Buckaroo Banzai's answer that for others running .NET applications in a docker container, the package update-ca-certificates might not even be installed by default.
Get shell access by executing this command:
sudo docker exec -it containerName /bin/bash
And execute this to update package lists, install and run update-ca-certificates:
apt update && \
apt install -y ca-certificates && \
update-ca-certificates
Now i can communicate with my(and others) web api again from my .NET application running inside the container.
To make this permanent you should add this snippet to your dockerfile for the image used before the ENTRYPOINT.
RUN \
apt update && \
apt install -y ca-certificates && \
update-ca-certificates
Find a great explanation to why this issue occurs and get a better understanding of how Certificate Authority works here:
What is CA certificate, and why do we need it?
question: with minio sdk,I have error:
MinIO API responded with message=Connection error: The SSL connection could not be established, see inner exception.. Status code=0, response=The SSL connection could not be established, see inner exception., content=
my Answer:
I registed self-signed certificate:
Import-Certificate -FilePath “public.crt” -CertStoreLocation 'Cert:\LocalMachine\Root'
system echo:
PSParentPath:Microsoft.PowerShell.Security\Certificate::LocalMachine\Root
Thumbprint Subject
E021FFABABAF32318D31BB18867F3BFAB788FFC0 OU=BETVSYS\andy#andy-tp (andy), O=Certgen Development
so problem resolved. don't change any code.
ref link:
1 https://github.com/minio/certgen
2 https://github.com/minio/minio-go/issues/1000#issuecomment-550262204

An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake

I wanted to use a self signed certificate to can use gRPC dotnet, but I get this error when I call the service from my client: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake.
I have created the pfx certificate with this script:
#echo off
set path="C:\Program Files\OpenSSL-Win64\bin"
#set OPENSSL_CONF=D:\programas\OpenSSL-Win64\OpenSSL-Win64\bin\openssl.cfg
#CA
echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096
echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 36500 -key ca.key -out ca.crt -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"
#SERVER
echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server.key 4096
echo Generate server signing request:
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%COMPUTERNAME%"
echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 36500 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt
echo Remove passphrase from server key:
#openssl rsa -passin pass:1111 -in server.key -out server.key
#CLIENT
echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client.key 4096
echo Generate client signing request:
openssl req -passin pass:1111 -new -key client.key -out client.csr -subj "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=%CLIENT-COMPUTERNAME%"
echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 36500 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
#Se crea el certificado pfx
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt
echo Remove passphrase from client key:
#openssl rsa -passin pass:1111 -in client.key -out client.key
pause
In my service, I use this code:
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps("server.pfx", "1111");
//listenOptions.UseHttps("<path to .pfx file>", "<certificate password>");
});
});
webBuilder.UseStartup<Startup>();
webBuilder.UseStartup<Startup>();
});
In my client I have this code:
X509Certificate2 miCertificado = new X509Certificate2("client.pfx", "1111");
HttpClientHandler miHandler = new HttpClientHandler();
miHandler.ClientCertificates.Add(miCertificado);
HttpClient miHttpClient = new HttpClient(miHandler);
GrpcChannelOptions misOpciones = new GrpcChannelOptions() { HttpClient = miHttpClient };
var miChannel = GrpcChannel.ForAddress("http://1.1.1.2:5001");
var miClient = MagicOnionClient.Create<IInterface>(miChannel);
ComponentesDto miDataResultado = await miClient.GetDataAsync();
I don't see how it could be the problem. What am I doing wrong?
Thanks.
This question is almost one and half year, and since that, I have done many tries until I could solve my problem.
I don't remember the exact way how I solved the problem, but I will modify this answer if it is needed according to the comments.
My solution is using .NET 6, that is the last stable verstion actually, but I guess that some parts could be work with .NET 5, but keep in mind that this solution is using .NET 6.
One important part in the handshake is the creation of the certificate, and more when it is self signed certificate. My solution works with .crt files instead of the pfx files that I tried to use in my original question.
About the server certificate, one important thing is that it has to have in the SAN the IP of the server, or the URL of the server. You can set both. If you open your .crt file with the notepad for example, you will see something like that:
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
44:C4:BD:F
X509v3 Authority Key Identifier:
keyid:0F:58:2
DirName:/CN=gRPC-CA
serial:22:A3
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
IP Address:192.168.1.100
Signature Algorithm: sha512WithRSAEncryption
In the last 3 lines, you can see that I have in the SAN (Subject Alternative Name), the IP of the server.
To create the certificates, I used easy rsa 3, that it is a tool that make easier to generate self signed certificates. You can find the tool here: https://github.com/OpenVPN/easy-rsa and you can download the binaries in the release section: https://github.com/OpenVPN/easy-rsa/releases.
The general steps are:
1.- copy the file vars.example in the same folder and call it "vars" (with no exetnsion).
2.- Edit the vars file with the information that you need.
3.- Run EasyRSA-Start.bat to start the shell of easy rsa.
4.- Run easyrsa init-pki to init the evironment. IMPORTANT: only do once, because if not you will delete all the certificates that you could have.
5.- Run easyrsa build-ca to create the CA certificate.
6.- Run easyrsa build-server-full 192.168.1.2 nopass to create the certificate for the server. IMPORTANT: change the IP for the IP of your server.
7.- Run easyrsa build-client-full Cliente01 nopass to create the certificate for the client. IMPORTANT: change the name Cliente01 to the name of your certificate. The name you put here, it will be the common name of the certificate.
All the certificates are created in the subfolder PKI.
I am hosting my service in a ASP application. Using minimal API, in the program.cs file of the ASP project I have to create a X509 certificate in this way:
builder.WebHost.ConfigureKestrel((context, options) =>
{
string miStrCertificado = File.ReadAllText("certificados/server.crt");
string miStrKey = File.ReadAllText("certificados/server.key");
X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);
//it is needed to create a second certificate because if not, you will get an error.
//Here you can find information about the issue: https://github.com/dotnet/runtime/issues/74093
X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));
//For security, delete the first certificate.
miCertficadoX509.Dispose();
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(miCertificado2);
});
});
And this is the client of grpc:
public async Task<List<FacturasDTO>> MymethodAsync()
{
try
{
//Con certificados
string miStrCertificado = System.IO.File.ReadAllText(#"Certificados\Client.crt");
string miStrClave = System.IO.File.ReadAllText(#"Certificados\Client.key");
string miStrCA = System.IO.File.ReadAllText(#"Certificados\ca.crt");
X509Certificate2 cert = X509Certificate2.CreateFromPem(miStrCertificado, miStrClave);
HttpClientHandler miHttpHandler = new HttpClientHandler();
miHttpHandler.ClientCertificates.Add(cert);
miHttpHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
miHttpHandler.ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation;
HttpClient httpClient = new(miHttpHandler);
GrpcChannel channel = GrpcChannel.ForAddress("https://20.30.40.50:5001", new GrpcChannelOptions
{
HttpClient = httpClient
});
var client = channel.CreateGrpcService<IFacturasService>();
var reply = await client.GetFacturasDTOAsync();
return reply.Facturas;
}
catch (RpcException ex)
{
throw;
}
catch (Exception ex)
{
throw;
}
}
/// <summary>
/// This method has to implement the logic that ensure that server certificate is a trust certificate.
/// This is needed because the server certificate is a self signed certificate.
/// </summary>
/// <param name="requestMessage"></param>
/// <param name="paramCertificadoServidor"></param>
/// <param name="chain"></param>
/// <param name="sslErrors"></param>
/// <returns></returns>
private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2 paramCertificadoServidor, X509Chain chain, SslPolicyErrors sslErrors)
{
return true;
//GetCerHasString devuelve la huella
//if (paramCertificadoServidor.GetCertHashString() == "xxxxxxxxxxxxxxxx")
//{
// return true;
//}
//else
//{
// return sslErrors == SslPolicyErrors.None;
//}
//// It is possible inpect the certificate provided by server
//Console.WriteLine($"Requested URI: {requestMessage.RequestUri}");
//Console.WriteLine($"Effective date: {certificate.GetEffectiveDateString()}");
//Console.WriteLine($"Exp date: {certificate.GetExpirationDateString()}");
//Console.WriteLine($"Issuer: {certificate.Issuer}");
//Console.WriteLine($"Subject: {certificate.Subject}");
//// Based on the custom logic it is possible to decide whether the client considers certificate valid or not
//Console.WriteLine($"Errors: {sslErrors}");
//return sslErrors == SslPolicyErrors.None;
}
Note that the method ServerCertificateCustomValidation() in the client always return true, it is for testing, but you should to implement the logic to ensure the certificate of the server is a trust certificate. I leave some comment code as example how could it be checked, but it is not tested, so I can't ensure it works or if it is the correct way to do. It is just to have some ideas.
I hope this could solve the problem. If not, leave any comment and I will update the solution.

Can a .p12 file with CA certificates be used in C# without importing them to certificate store

I got a .p12 certificate file with 3 certificates in it. 2 of them are CA certificates.
If I use curl (7.70 on Win10) I can do:
curl -s -S -i --cert Swish_Merchant_TestCertificate_1234679304.p12:swish --cert-type p12 --tlsv1.2 --header "Content-Type:application/json" https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests --data-binary #jsondata.json
Curl will use the CA certificates in the p12 file when connecting to the server.
On the other hand, if I try to do something similar in .net core (3.1) it fails with the error message "The message received was unexpected or badly formatted."
var handler = new HttpClientHandler();
var certs = new X509Certificate2Collection();
certs.Import(#"Swish_Merchant_TestCertificate_1234679304.p12", "swish", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
foreach (var cert in certs)
{
handler.ClientCertificates.Add(cert);
}
var client = new HttpClient(handler);
var url = "https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests";
var request = new HttpRequestMessage()
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
};
request.Content = new StringContent(System.IO.File.ReadAllText(#"jsondata.json"), Encoding.UTF8, "application/json");
request.Headers.Add("accept", "*/*");
var response = await client.SendAsync(request);
Using Wireshark I saw that curl sends all three certificates from the p12 file whereas .net core only sends one. See images below.
If I install the CA certificates into "Personal certificate" for "Current User" then .net core also sends all three certificates and it works.
Question: Do I have to install the CA certificates (into the certificate store) when using .net core or is there a way to make it behave just like curl which uses the certificates from the p12 file?
Wireshark curl:
Wireshark .net core:
Short answer: no*.
Wordier intro: SslStream picks one certificate out of the ClientCertificates collection, using data that (was historically, but no longer generally) is sent by the TLS server about appropriate roots (and if none is applicable then it picks the first thing where HasPrivateKey is true). During the selection process each candidate certificate is checked in isolation, and it asks the system to resolve the chain. On Windows, the selected certificate is then sent down to the system libraries for "we're doing TLS now", which (IIRC) is where the limitations come from. (macOS and Linux builds of .NET Core just try to maintain behavioral parity)
Once the certificate is selected, there's one last chain-walk to determine what certificates to include in the handshake, it's done without the context of anything else from the ClientCertificates collection.
If you know that your collection represents one chain, your best answer is to import the CA elements into your user CertificateAuthority store. That store does not impart any trust to the CA certificates, it's really just a cache that's used when building chains.
Also, you don't want PersistKeySet, and probably don't want MachineKeySet: What is the rationale for all the different X509KeyStorageFlags?
var handler = new HttpClientHandler();
using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
var certs = new X509Certificate2Collection();
certs.Import(#"Swish_Merchant_TestCertificate_1234679304.p12", "swish", X509KeyStorageFlags.DefaultKeySet);
foreach (X509Certificate2 cert in certs)
{
if (cert.HasPrivateKey)
{
handler.ClientCertificates.Add(cert);
}
else
{
store.Add(cert);
}
}
}
var client = new HttpClient(handler);
...
* If your system already has the CA chain imported, it'll work. Alternatively, if the CA chain uses the Authority Information Access extension to publish a downloadable copy of the CA cert, the chain engine will find it, and everything will work.

iOS Push Notifications AuthenticationException for Certificate

I am trying to use PushSharp to send a push notification to my app. I have two Apple accounts... one is a regular account, and the other is an Enterprise account. I have a developer certificate on the regular account that works, but both my development and distribution certificates fail to work from the Enterprise account. I get an Authentication Exception..
A call to SSPI failed, see inner exception.
Inner Exception:
[System.ComponentModel.Win32Exception]: {"An unknown error occurred while processing the certificate"}
This occurs in this code of PushSharp (I didn't comment the line out):
try
{
stream.AuthenticateAsClient(this.appleSettings.Host, this.certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
//stream.AuthenticateAsClient(this.appleSettings.Host);
}
catch (System.Security.Authentication.AuthenticationException ex)
{
throw new ConnectionFailureException("SSL Stream Failed to Authenticate as Client", ex);
}
Here is the code from my test project:
public static void SendPingToApple()
{
try
{
var devicetoken = "mytoken";
var appleCert = File.ReadAllBytes(AssemblyPathName + #"\Resources\DistPrivKey1.p12");
var push = new PushBroker();
push.RegisterAppleService(new ApplePushChannelSettings(IsProduction, appleCert, "password"));
push.QueueNotification(new AppleNotification()
.ForDeviceToken(devicetoken.ToUpper())
.WithAlert("Test Notification"));
push.StopAllServices();
}
catch (Exception ex)
{
throw;
}
}
Convert your ssl certificates to pem format using following commands
openssl pkcs12 -in yourP12File.pfx -nocerts -out privateKey.pem
openssl pkcs12 -in yourP12File.pfx -clcerts -nokeys -out publicCert.pem
Then run following command to ensure that there is no issue with your certificate or network connection.
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert
YourSSLCertAndPrivateKey.pem -debug -showcerts -CAfile "Entrust.net
Certification Authority (2048).pem"
You should download Entrust certificate and convert it to pem as APNS certificates are signed by Entrust.

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)?
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];
}
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
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.

Categories

Resources