Unity SSLStream AuthenticateAsClient gives SslPolicyErrors.RemoteCertificateNotAvailable - c#

I'm developing an application using Secure Sockets in Unity5 (in C#) to allow a server to push information to clients. I want to use Socket and SSLStream for this, to keep the communications secure.
I have an elastic beanstalk environment setup with a certificate provided by the AWS certificate service.
I've written a client class that can connect to this environment with a RemoteCertificateValidationCallback below:
public static bool RemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
bool isOk = true;
// If there are errors write them out
if (sslPolicyErrors != SslPolicyErrors.None)
{
//Debug.Log(sslPolicyErrors.ToString());
Console.WriteLine(sslPolicyErrors.ToString());
return false;
}
return isOk;
}
When I run this code from a Console application created in Visual Studio 2017 I get sslPolicyErrors.None as expected.
Running the exact same code from within Unity5 (minus Console references) gives me sslPolicyErrors.RemoteCertificateNotAvailable.
I've tried importing the certificates to the Mono trust store using mozroots -import --machine which gives the following output:
Issuer: C=US, O=Amazon, OU=Server CA 1B, CN=Amazon
Serial number: 66-xx-xx-xx-xx-xx-xx-xx-xx-xx-46-xx-xx-xx-xx-0D
Valid from 05/05/2017 00:00:00 to 05/06/2018 12:00:00
I've been hunting around but can find nothing that would suggest why this works as a standalone console program, but not when run from Unity. If I ignore this sslPolicyError specifically, everything works just fine, communication between the client and server happens as expected. But I don't want to be randomly ignoring an error that could mean the communications could be compromised.
I'm really hoping someone here has some idea!

why this works as a standalone console program, but not when run from Unity?
As Mono official said, Mono doesn’t include root certificates while standalone program using system certs.
There is one solution that install the cert via X509Store.
string cert = "BASE64 ENCODED CERT(PEM), NOT HEADER AND FOOTER, BASICALLY EXPORTED BY OPENSSL";
byte[] certBytes = Convert.FromBase64String(cert);
X509Certificate2 certificate = new X509Certificate2(cert);
X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
You can use openssl to export a pem, like openssl x509 -inform DER -in YOUR_CER.cer -out YOUR_PEM.pem.
Once the proper cert is installed, RemoteCertificateValidationCallback won't return RemoteCertificateNotAvailable anymore if connecting to trusted sites.

Related

How to proper validate SSL certificate with X509Certificate2 on Mono and multiple platforms

I have to validate several SSL certificates in a none-browser application for HttpRequests and websocket connections which should run on IOS, Android and Linux.
When a connection via HTTPS happens I receive an array of X509Certificate2 objects where the bottom one is the server certificate and the most top one the root CA (hopefully). As an example, when I connect to https://google.com I receive 3 X509Certificate2 with the following SubjectName.Name:
0 : "CN=google.com, O=Google Inc, L=Mountain View, S=California, C=US"
1 : "CN=Google Internet Authority G2, O=Google Inc, C=US"
2 : "CN=GeoTrust Global CA, O=GeoTrust Inc., C=US"
I now need to verify the certificate validation with the given information and the following verifications:
Chain-of-trust verification
Hostname verification
Certificate revocation verification
What I tried and not understood and failed:
When I call the X509Certificate2.Verify() method on each certificate independently it returns false everytime. I also do not understand why it can return anything else then false because the verification happens independently. Instead the complete chain, meaning all certificates, should be checked as far as I understood the theory.
I then used the X509Chain class:
foreach (X509Certificate2 cert in allthreecerts)
{
X509Chain chain = new X509Chain();
X509ChainPolicy chainPolicy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.Offline,
RevocationFlag = X509RevocationFlag.EntireChain
};
chain.ChainPolicy = chainPolicy;
if (!chain.Build(cert))
{
foreach (X509ChainElement chainElement in chain.ChainElements)
{
foreach (X509ChainStatus chainStatus in chainElement.ChainElementStatus)
{
Debug.WriteLine(chainStatus.StatusInformation);
}
}
}
}
This prints out RevocationStatusUnknown and OfflineRevocation for each certificate.
Again, I do not understand why this should work since a chain is build which each certificate independently. Should not be the parent certificate be the Issue of the child certificate all down to the root CA?
What I think I need somehow but do not know how.
In order to verify the certificate revocation all clients need to provide a certificate revocation list. Where do I get such a list and where to I load it and tell the chain to use this local list?
The same problem is the Chain-of-trust verification since the last certification should be a root certification and must be one of the clients trusted root CA's. So I have to load, for example, all root CA which are shipped with Firefox and check if the root CA is one of them? What is the best way to do that?
[Update]
I made a copy paste mistake in the google.com example where I pasted two identical certificate subject names.
When a connection via HTTPS happens I receive an array of X509Certificate2 objects where the bottom one is the server certificate and the most top one the root CA (hopefully).
If you really did get root ca certificate from SSL server then the SSL server is not configured correctly. It should return its server certificate and the whole chain Except the root CA certificate.
When I call the X509Certificate2.Verify() method on each certificate independently it returns false everytime.
As stated in the documentation of X509Certificate2.Verify method This method builds a simple chain for the certificate and applies the base policy to that chain. Now what is the base policy I do not know. But on of the default values of ChainPolicy is RevocationMode = X509RevocationMode.Online. As for Mono the source of this method can be found here. The source of Mono implementation of X509Chain can be found here.
This prints out RevocationStatusUnknown and OfflineRevocation for each certificate.
What else should it print when you specified RevocationMode = X509RevocationMode.Offline and there are no CRLs or OCSP responses in cache (probably)?
In order to verify the certificate revocation all clients need to provide a certificate revocation list. Where do I get such a list and where to I load it and tell the chain to use this local list?
Each certificate (well except root ca certificate) in the chain contains a link (or links) to CRL. .NET and most likely Mono too has an implementation that will find link to CRL in the certificate, will download it and will check if the certificate is not revoked.
The same problem is the Chain-of-trust verification since the last certification should be a root certification and must be one of the clients trusted root CA's. So I have to load, for example, all root CA which are shipped with Firefox and check if the root CA is one of them? What is the best way to do that?
No, RevocationFlag = X509RevocationFlag.EntireChain will do that for you using some store that mono uses. I don't know if it is the Firefox store but on Linux it has its own store and you can import root ca certificates from Firefox store. Check ChainElements and see for yourself what certificates did it find.
I would suggest that you build the chain with SSL server certificate only (as this will check all certs in the chain upwards) with RevocationMode = X509RevocationMode.Online and RevocationFlag = X509RevocationFlag.EntireChain. I would also try to set ExtraStore property of X509Chain to a list of certificate you got from SSL server. After the Build method I would chceck ChainStatus property of X509Chain object which is an array of statuses. I would pick all statuses that have not set X509ChainStatus.Status to NoError. If there will be any such status I would throw and log each X509ChainStatus.Status and X509ChainStatus.StatusInformation.
HTH

Installing a pfx file with C# seems different to manually installing it

I have a c# program which installs a pfx file (Code below)
X509Certificate2 cert;
cert = new X509Certificate2(#"myCert.pfx", "password");
if (cert != null)
{
var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(cert))
{
store.Add(cert);
}
}
This seems to add the cert into the correct place when I view it in the management console. However I have a websocket server in c# that will pick it out of the store and use it for its ssl connections to the browser however the browsers all fail due to not having authentication to the cert.
However if I were to install it manually (clicking the cert) and installing it to the same location everything works fine.
NOTE:It is Self-Signed
How would I fix this problem
If the certificate is self-signed, you have to use it as well for the HTTPS page that holds the javascript code connecting to the websocket, so the browser can prompt you to accept the certificate.
You can retrieve the certificate using for example:
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificate = store.Certificates[1];
store.Close();
And then use it for your WebSocket server. I don't know which .NET WebSocket server are you using, but in WebSocketListener is done this way.

PushSharp APNS Service Stop working after windows shutdown

I can send notifications to my iPhone device succeffully using Push Sharp via sandbox APNS server but I am having a problem.
I have generated .cer and .p12 files and then installed them on my windows 8 development machine successfully.
I used this tutorial to install the certificates on my windows 8 machine.
Yesterday things were working fine and I was sending the notification successfully. I shutdown my system and then next day when I try to run the code I was getting following exception:
the message was unexpected or badly formatted pushsharp
I tried different solution available on Google but nothing helped. Then I delete the certificates from my machine and then re-install them and things started to work again.
In order to make the service fool proof I shutdown the system to check if notification sending fails or not, and yes it fail again with the same exception.
I again deleted the certificates and re-install them to correct the issue. I do not know whats the actuall problem? what makes PUSHSharp to stop sending notification after shutdown.
Note: Windows firwall is disabled.
Any idea?
I have been working with PushSharp for the past few weeks and have not had this problem. My environment is Windows 7 however. After you've created the appropriate Push Notification Certificate in iPhone Developer Program Portal you should have downloaded a file named something like apn_developer_identity.cer. If you have not done so already, you should open/import this file into Keychain, into your login section.
Finally, if you filter Keychain to show your login container's Certificates, you should see your Certificate listed. Expand the certificate, and there should be a Key underneath/attached to it.
Right Click or Ctrl+Click on the appropriate Certificate and choose Export. Keychain will ask you to choose a password to export to. Pick one and remember it. You should end up with a .p12 file. You will need this file and the password you picked to use the Notification and Feedback Libraries here.
OpenSSL
Here is how to create a PKCS12 format file using open ssl, you will need your developer private key (which can be exported from the keychain) and the CertificateSigningRequest??.certSigningRequest
Convert apn_developer_identity.cer (der format) to pem:
openssl x509 -in apn_developer_identity.cer -inform DER -out apn_developer_identity.pem -outform PEM}
Next, Convert p12 private key to pem (requires the input of a minimum 4 char password):
openssl pkcs12 -nocerts -out private_dev_key.pem -in private_dev_key.p12
(Optional): If you want to remove password from the private key:
openssl rsa -out private_key_noenc.pem -in private_key.pem
Take the certificate and the key (with or without password) and create a PKCS#12 format file:
openssl pkcs12 -export -in apn_developer_identity.pem -inkey private_key_noenc.pem -certfile CertificateSigningRequest??.certSigningRequest -name "apn_developer_identity" -out apn_developer_identity.p12
Once you generate the p12 file using these steps you will not really need to snap it to your console. You will just need to make changes in your code as follows:
var appleCert = File.ReadAllBytes("C:/Certificate/aps_dev_identity.p12");
Hope this helps.
I have been working on MOON APNS since 2012 and it's working fine but from last few days i am getting below error message
Error Message : System.Security.Authentication.AuthenticationException: A Call to SSPI failed, see inner exception.
System.ComponentModel.Win32Exception: The message received was
unexpected or badly formatted
Solution : in PushNotification.cs file replace
_apnsStream.AuthenticateAsClient(host, certificates, System.Security.Authentication.SslProtocols.Ssl3, false);
with
_apnsStream.AuthenticateAsClient(host, certificates, System.Security.Authentication.SslProtocols.Tls, false);
As, I didn't find any confirmation from apple side but from github.com I found solution for this and it's work for us.
It's seems that apple depricate "unsafer" protocol SSL.
Check first if you can do the notifications with C# code like this one first, then worry about the installation. I had the same message when I was trying to execute the code, I didn't care about installation, and solved it by making sure that I use a certificate of type .p12 and not .pem and to make sure that the .p12 had a password. I can now send notifications to my iPhone from a console C# app in my pc.

How to get Thumbprint or Public Key of Issuer Certificate?

We have created a self signed CA certificate which we use to sign other certificates for SSL purposes. These certificates will be installed in other servers which we do not have access to and will be strictly to communicate with other clients like mobile applications.
When these clients (written in .NET) make a request to the servers using HTTPS we get the "Invalid certificate received from server" error because the CA cert is not a trusted CA on that client.
We want to bypass this security using the ServicePointManager.ServerCertificateValidationCallback, but only if the certificate being used was signed by our CA certificate.
I can check the certificate.Issuer, but that can easily be spoofed by anyone. How can I get the Thumbprint or Public Key of the Issuer certificate of the invalid certificate? If I can get access to that I can easily compare it to the one I know is valid and ignore the certificate error and continue on with the request.
UPDATE
I think I am getting closer. It looks like what we're looking to do is not doable so went a slightly different direction.
Using the X509Chain we can verify whether the certificate is a child of the CA using the code below:
var caCert = new X509Certificate2(#"[path]\MyCA.cer");
var newChain = new X509Chain();
newChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
newChain.ChainPolicy.ExtraStore.Add(caCert);
var res = newChain.Build(certInQuestion);
Build() still returns false (as expected because the CA is not trusted on the client), but now newChain.ChainStatus[0].Status is returning UntrustedRoot. Based on my testing this means the chain validated because if I supply a different CA Certificate it fails with InvalidChain.
In conclusion, that tells me that if the Status is UntrustedRoot, the certificate was created with our CA certificate and thus it's valid, anything else it's a fake one!
Are my assumptions correct?
That's the wrong solution. You should install your self-signed certificate as a trusted CA certificate in all clients, or better still just get it signed by a CA that is already trusted. Don't write code for this.
Here are some links with info and some tools to generate free public and private keys:
https://www.igolder.com/pgp/
https://www.igolder.com/pgp/generate-key/
I think EJP's solution is the most acceptable. #EJP, can you give us some examples of applications that can help us become a "tiny CA".
I'm not entirely sure this this is what you're looking for but it might push you in the right direction. Here's a PowerShell script I use to find a cert that I just created and extracted using MAKECERT.EXE & CERTMGR.EXE:
# get certificate thumbprint
$appCertificate = Get-PfxCertificate -FilePath $certificateFullPath
Write-Host " .. adding certificate to local machine root" -ForegroundColor Gray
& $ExeCertManager /add $certificateFullPath /s /r localMachine root
Write-Host " Certificate installed on local machine" -ForegroundColor Gray
Write-Host " .. exporting private key for certificate" -ForegroundColor Gray
Get-ChildItem cert:\\localmachine\my | Where-Object {$_.Thumbprint -eq $appCertificate.Thumbprint} | ForEach-Object {
$CertPfxName = (Get-Item -Path $certificateFullPath).BaseName
}
It seems that a possible solution would be that you could email the certificate, according to this answer: https://stackoverflow.com/a/4473799/674700.

How to load another's certificate to my local certificate store?

I have a certificate (.pem file) that is distributed by another service vendor. I downloaded the certificate from the vendor and saved it to my local drive. In my WCF client, I am trying to load this certificate from the local drive and it is giving me an error “The private key is not present in the X.509 certificate” when communicating with the service. I was told that I need to load this certificate to my local certificate store to resolve this error. Can anyone provide some directions? Thanks!
I have the below function to load certificate from the path specified in the file parameter.
public static X509Certificate LoadCertificate(string file)
{
try
{
return X509Certificate.CreateFromCertFile(file);
}
catch (System.Security.Cryptography.CryptographicException)
{
string filestr = File.ReadAllText(file);
StringBuilder sb = new StringBuilder(filestr.Remove(0, filestr.IndexOf("-----BEGIN CERTIFICATE-----")));
sb.Replace("-----BEGIN CERTIFICATE-----", "");
sb.Replace("-----END CERTIFICATE-----", "");
//Decode
try
{ //see if the file is a valid Base64 encoded cert
byte[] certBytes = Convert.FromBase64String(sb.ToString());
return new X509Certificate(certBytes);
}
catch (System.FormatException)
{
throw;
}
}
}
In my WCF client, it is loading the certificate that was created from LoadCertificate() function.
public X509Certificate Certificate { get; set; }
ClientCredentials loginCredentials = new ClientCredentials();
loginCredentials.UserName.UserName = this.UserId;
loginCredentials.UserName.Password = this.Password;
loginCredentials.ClientCertificate.Certificate = new X509Certificate2(this.Certificate);
Your code says you are trying to use the certificate to authenticate the client to the server, in addition to providing a username and a password. That's pretty bizarre but I guess possible. You will need the private keys associated with that certificate for that purpose, as the client will need them to encrypt the communication so the server can use the certificate to decrypt and verify that the client is legit. A .pem file can contain both public and private keys but maybe the one that was sent to you does not?
My guess is that really you only wanted the client to connect to a server that is using this certificate to identity itself and encrypt the communication. If so, all the client needs to do is import the certificate locally so it can compare against this local version when the server sends it when the client first connects to it.
Do to that, Microsoft made double clicking on a .pem file in a file browser start the certificate import wizard. But in case that does not work for you, here is the hard way:
Start - run - mmc
File - Add/Remove snap-in
Select "certificates" - click Add - choose Computer Account - Local computer
Close snap-in window with OK
Now browse to Certificates (Local computer) - Personal - Certificates
Right click - All Tasks - Import

Categories

Resources