Adding a CA certificate for client certificates - winapi

I'm writing a server application that uses CryptoAPI and Schannel for setting up a secure SSL connection to clients. The server requires the clients to submit a certificate for verification (by setting the ASC_REQ_MUTUAL_AUTH flag in AcceptSecurityContext).
The problem I have is that some clients (namely clients using javax.net.ssl) does not pass along their client certificate (even though it's been configured to do so). I suspect this is because the CA certificate used for signing the client certificates are not in the list of CA's passed to the client during the handshake.
I've tried to do variations of the following to add the CA certificate to this list:
PCERT_CONTEXT caCertContext = ...; /* Imported from a DER formatted file */
HCERTSTORE systemStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_LOCAL_MACHINE,
L"ROOT");
bool ok = CertAddCertificateContextToStore(
systemStore,
caCertContext,
CERT_STORE_ADD_USE_EXISTING,
NULL);
if (!ok)
{
std::cerr << "Could not add certificate to system store!" << std::endl;
}
In the above example CertAddCertificateContextToStorealways fails. If I change CERT_SYSTEM_STORE_LOCAL_MACHINEto CERT_SYSTEM_STORE_CURRENT_USER I am presented with a popup asking me to confirm the certificate, but even if I accept the CA certificate will not appear in the list sent to the client.
I also tried extending the system store collection with a temporary memory store (something I picked up from here) but to no avail.
Anyone know of a way to solve this? Ideally programmatically without using any GUI or external tool?

You are getting that error because you don't have permission to access the store as read and write, you can only access it as read. So what you have to do is add CERT_STORE_READONLY_FLAG so it will be:
HCERTSTORE systemStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG ,
L"ROOT");
If you which to make changes to your store and not have it read only that means you will require administration elevation when you are running your C++ application.

If you don't want to add it systemwide (as you mentioned in the comment) you can open with the CERT_SYSTEM_STORE_CURRENT_USER flag.
CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_STORE_OPEN_EXISTING_FLAG |
CERT_SYSTEM_STORE_CURRENT_USER, L"MY");

Related

Xamarin Android: Native TLS, ClientWebSocket, with Custom Certificate Authority Certificate Validation Fails

Client Environment is Xamarin Android Native TLS 1.2 SSL/TLS implementation (boringssl aka btls), using System.Net.WebSockets.ClientWebSocket. This is running on an Android 7.0 device. Visual Studio 2017 15.8.1, Xamarin.Android 9.0.0.18.
Server Environment is Windows .NET 4.7 running Fleck (WebSocket server) configured with TLS 1.2 using a certificate issued by a homemade (non-trusted anywhere on the globe) Certificate Authority (CA).
Assuming a homemade CA Cert (.pem or .cer format) has been installed on the android device via Settings->Security->Install from SD Card, the ClientWebSocket connects using TLS 1.2 without problems, as one would expect. Since this is a global solution to a local (one part of my app) problem, not to mention opening a security hole for the larger device ecosystem, I do not wish to require this setup.
I have then tried several methods to localize the trust of the CA to only my application without success. Regardless of approach, there is always the same exception thrown by ClientWebSocket.ConnectAsync(): A call to SSPI failed and Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED at /Users/builder/jenkins/workspace/xamarin-android-d15-8/xamarin-android/external/mono/external/boringssl/ssl/handshake_client.c:1132
I created a sample windows server and console app and Xamarin.Forms Android app that demonstrates the issue and the attempts to workaround it described below. Included is a custom CA cert. The server code dynamically issues a client cert with SANs bound to your IP/hostnames for ease of repro.
Attempt 1:
Apply the android:networkSecurityConfig="#xml/network_security_config" attribute to the application element in the AndroidManifest.xml file, including resources Resources\raw\sample_ca.pem Resources\xml\network_security_config.xml
<?xml version="1.0" encoding="utf-8" ?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="#raw/sample_ca"/>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>
This had no visible effect, and I cannot see anything in the debug output that would indicate that the runtime is even loading this. I have seen references to messages like this:
D/NetworkSecurityConfig: No Network Security Config specified, using platform default
However, with or without this in place I have never seen messages like this or similar. I really have no idea if it is being applied or not, or if btls implementation even uses/respects this.
Interestingly, since the Android minSdk is set to 24 and target sdk of 27 I would expect the lack of this declaration should cause TLS 1.2 to not work if I simply added the CA to the android device user certificate store. I suspect there are a few Xamarin bugs surrounding this.
Attempt 2:
Add the CA to the X509 Store, hoping btls uses that as a source of certificates. This approach works on Windows/.NET 4 (it does bring up a dialog to accept the addition of the certificate).
X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, false);
if (certs.Count == 0)
store.Add(cert);
store.Close();
Attempt 3:
Handle ServerCertificateValidationCallback. This never gets called in Xamarin Android, but this approach works on Windows/.NET 4.
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{
if (errors == SslPolicyErrors.None)
return true;
//BUGBUG: Obviously there should be a much more extensive check here.
if (certificate.Issuer == caCert.Issuer)
return true;
return false;
};
There are some Mono issues surrounding btls and a pull request that makes this approach look possible in the near future.
Attempt 4:
Add the CA cert (and/or the cert issued from the CA) to the ClientWebSocket.Options ClientCertificates collection. Technically this should not work with CA Certs but should be the approach to use with self-signed certificates. I offer it here merely for completeness. As expected, it does not work either.
Full Repro Code
Easy to use code that demonstrates this issue with all of the attempted workarounds described above is available on GitHub.
I don't know, what the problem was, but I also had some problems, as I have started with my first https webservices.
The reason was, that I have started with a self signed certificate, what does not work without ugly workarounds...
So.. only use signed (trusted) certificates from a public vendor and you should not have any problem...
To switch between http and https you only have to change the url (from http to https) - no further changes in the app ae needed.
I normally do first (local) tests with the web service with http (the url ist loaded from an .ini file) and then copy the web service to the "real" webserver (with certificate and https url).
I never had any problems (when a trusted certificate is used)...

How to configure node.js running on Windows to use multiple SSL certificates with multiple domain names?

I've read through this question and answer: "
Is it Possible to Dynamically Return an SSL Certificate in NodeJS?"... but it uses .key and .crt files for the domains and the server.
On a Windows 2008 R2 machine, I can't find the domain1.key, server.key and server.crt files. Instead I've created a domain1.pfx file by exporting the SSL certficate from IIS.
I am able to successfully run an https node.js server using this one PFX file with one domain like this:
var fs = require('fs');
var https = require('https');
var crypto = require('crypto');
function getSecureContext(domain) {
return crypto.createCredentials({
pfx: fs.readFileSync('/path/to/' + domain + '.pfx'),
passphrase: 'passphrase'
}).context
}
var secureContext = {
'domain1': getSecureContext('domain1')
}
var options = {
SNICallback: function (domain) {
return (secureContext.hasOwnProperty(domain) ? secureContext[domain] : {});
},
pfx: fs.readFileSync('/path/to/domain1.pfx'); // for the server certificate
};
var server = https.createServer(
options,
requestListener).listen(443);
However what if I have a multiple domain certificate plus another certificate for a single domain, how would the SNICallback and the getSecureContext functions be configured to have each domain name use the correct certificate?
I think the server certificate should be the same for both PFX files since they are on the same server so I'm using only the first PFX file (for domain1) as the server certificate.
I've tried changing the secureContext object like this:
var secureContext = {
'domain1': getSecureContext('domain1'),
'domain2': getSecureContext('domain2'),
.
.
}
This gives me the error "listen EACCES'.
In my specific situation I have two SSL certificates. One is an extended validation certificate for one domain name, and the second is a multiple domain certificate supporting five domain names.
I've found it very difficult to debug the EACCES error. There doesn't seem to be more detail as to what is causing the EACCES. Is my configuration wrong, is there a problem with the certificates? I do know that these certificates work correctly when I use them in IIS running an IIS server (instead of a node.js server) on the same Windows 2008 R2 server.
I would like to stay with a pure windows and node.js configuration. (Not nginx, iisnode or any other libraries if possible).
Solved it. The EACCES error was due to my not listing all the sites that need to use the two certificates. Since I was testing, I only was working with two site names, but the multi-domain certificate includes some other sites. Each site needs to be listed as below. Otherwise one or more of the sites will not have a certificate associated with it causing the EACCES error.
var secureContext = {
'domain1': getSecureContext('domain1'),
'domain2': getSecureContext('domain2'),
'domain3': getSecureContext('domain2'),
'domain4': getSecureCOntext('domain2')
}

Phonegap Android app ajax requests to HTTPS fail with status 0

Ajax HTTPS requests from my PhoneGap/Cordova app on Android inexplicably fail with status=0. It appears only when signing the app with the release key (i.e., exporting from ADT), but doesn't appear when signing with debug key (running directly in emulator or phone).
request = new XMLHttpRequest()
request.open "GET", "https://some.domain/", true
request.onreadystatechange = ->
console.log "** state = " + request.readyState
if request.readyState is 4
console.log "** status = " + request.status
request.send()
always outputs
** state = 4
** status = 0
It doesn't matter if i install the app from Play Store or with adb utility. I presume it could be connected with the certificate, since not all HTTPS domains fail this way.
I was having the same problem but my solution was a little different.
In only the Android App build of my Cordova app, AJAX calls to my server via HTTPS were being blocked. Not in iOS, not in desktop browsers. Most confusingly, in the actual Android Browser the HTTPS AJAX calls would work no problem.
I verified that I could make HTTPS AJAX calls to well known and trusted URLs such as https://google.com as well as regular HTTP calls to any URL I cared to try.
This led me to believe that my SSL cert was either NOT installed 100% correctly OR the cheap (~$10 usd) cert from PositveSSL was not universally trusted OR both.
My cert was installed on my AWS Load Balancer so I looked around about how I may have messed this up and also how PositiveSSL was not the best cert to be using in terms of trustworthiness. Lucky me found an article covering AWS ELB installation of certs AND they happened to be using a PositiveSSL cert! Contained within was this little gem:
"...Don’t be fooled by the AWS dialog, the certificate chain isn’t really optional when your ELB is talking directly to a browser..."
http://www.nczonline.net/blog/2012/08/15/setting-up-ssl-on-an-amazon-elastic-load-balancer/
Drumroll....
I reinstalled the cert with the "optional" Certificate Chain info and voilà!, the HTTPS AJAX calls to my server started working.
So it appears that the Android Webview is more conservative than the Android Browser in terms of cert trust. This is not totally intuitive since they are supposed to be basically the same tech.
It happens when the requested URL responds with an erroneous or self-signed certificate. While testing or distributing the app to friends, setting <application android:debuggable="true"...> in AndroidManifest.xml is enough — it automatically bypasses certificate errors.
But Google Play Store will not accept an APK with android:debuggable="true". First of all, the certificates, of course, need to be fixed. But while that happens, here is a workaround for PhoneGap/Cordova 3:
In your app package create a subclass for CordovaWebViewClient:
public class SSLAcceptingCordovaWebViewClient extends CordovaWebViewClient {
public SSLAcceptingCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
super(cordova, view);
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
}
Same for IceCreamCordovaWebViewClient:
public class SSLAcceptingIceCreamCordovaWebViewClient extends IceCreamCordovaWebViewClient {
public SSLAcceptingIceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
super(cordova, view);
}
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
}
in <Your App Name>.java add an override for makeWebViewClient:
#Override
protected CordovaWebViewClient makeWebViewClient(CordovaWebView webView) {
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
return new SSLAcceptingCordovaWebViewClient(this, webView);
} else {
return new SSLAcceptingIceCreamCordovaWebViewClient(this, webView);
}
}
Et voilà! SSL errors will be disregarded. However, never use erroneous certificates. Try to fix them first and use this dirty workaround only when you run out of other solutions.
The other option that works as well is to recompile the underlying cordova.jar file so that the test is removed completely thus no reason to worry about your cert being valid or not. I ran in the issue due to the fact that Android would not recognize the GoDaddy cert that was on the server. The cert shows valid on iOS but even when browsing from Android complained about the cert. This is from the 2.9.x branch as this is what I was working with.
cordova-android / framework / src / org / apache / cordova / CordovaWebViewClient.java
#TargetApi(8)
#Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
final String packageName = this.cordova.getActivity().getPackageName();
final PackageManager pm = this.cordova.getActivity().getPackageManager();
ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
handler.proceed();
return;
/* REMOVED TO BY PASS INVALID CERT CHAIN ****
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// debug = true
handler.proceed();
return;
} else {
// debug = false
super.onReceivedSslError(view, handler, error);
}*/
} catch (NameNotFoundException e) {
// When it doubt, lock it out!
super.onReceivedSslError(view, handler, error);
}
}
NOTE: I understand this is not safe but when all else fails this solved the issue that has been on going for over 2 months including reinstalling the cert following the cert chain install guide and beside it is a site that is our own not 3rd party so no matter if valid or not it is only connecting to this server.
In my case it has been a missing intermediate certificate, which I had to install on my webserver. You have to keep it in mind especially when you use cheap certificates.
You can check it easily online if your certificate chain is proper, you will find a lot on google, e.g. https://www.sslshopper.com/ssl-checker.html
At the Apache2 it's part of the VirtualHost 443 directive, you have three rules in your directive, it looks like that:
SSLCertificateFile /etc/apache2/ssl/mycert.crt
SSLCertificateKeyFile /etc/apache2/ssl/mykey.key
SSLCertificateChainFile /etc/apache2/ssl/certification_auth_intermediate.crt
You can't use relese-ready (phonegap) apks with self-signed certificates. Look at this answer to get further information.
lg
fastrde

OpenSSL WinINET client

I have a working client and a server which, once finished would have a valid SSL certificate.
At the moment, for the mean of testing, I'm just disabling the need of SSL verification in the client by adding the flags INTERNET_FLAG_IGNORE_CERT_CN_INVALID and SECURITY_FLAG_IGNORE_UNKNOWN_CA like this:
HINTERNET hRequest = HttpOpenRequest(hConnection,
"GET","index.html",
NULL,NULL,NULL,
INTERNET_FLAG_RELOAD|
INTERNET_FLAG_EXISTING_CONNECT
#ifdef __HTTPS__
| INTERNET_FLAG_SECURE |INTERNET_FLAG_IGNORE_CERT_CN_INVALID|SECURITY_FLAG_IGNORE_UNKNOWN_CA
#endif
,
dwContext);
I got this to work perfectly on my laptop. Now I'm trying to use it on my PC. same code exactly. copied and pasted the files (both Visual Studio Professional 2008), now I'm getting 12045 error. which means Invalid Certificate Authority
I tried disabling Windows Firewall, didn't work. both computer connected to the same router
Any ideas what can cause this?
Thanks!
EDIT
Basically what happens is the same as described here by Microsoft, only this article is for Windows CE
Does this help?
If a server SSL certificate is issued by unknown or invalid certificate authority WinInet HttpSendRequest API or MFC CInternetFile::SendRequest will fail with error 12045 (ERROR_INTERNET_INVALID_CA).
I think you should call InternetSetOption() on the request, before issuing it, like this, because the HttpOpenRequest() flags do not specify SECURITY_FLAG_IGNORE_UNKNOWN_CA:
HINTERNET hRequest = HttpOpenRequest(hConnection,...
#ifdef __HTTPS__
DWORD dwFlags;
DWORD dwBuffLen = sizeof(dwFlags);
InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, (LPVOID)&dwFlags, &dwBuffLen);
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
InternetSetOption (hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags));
#endif
HttpSendRequest(hRequest,...

Attach .pfx certificate to exe file

I have to deploy a software to n clients that will install a certificate and use it. I don't want to deploy two files (.pfx and exe) just one (.exe that will contain the .pfx).
What i'm doing now is import the certificate from a location.
X509Certificate2^ x509 = gcnew X509Certificate2;
x509->Import( "C:\\Tmp\\certficate.pfx" );
Is it possible ?
You could always embed the certificate data as a resource.
One warning though: if someone gets the executable, they can pull out the PFX file pretty easily.
Are you able to securely distribute the executable?
Here are some rough steps, distilled from: http://www.spikezilla-software.com/blog/?p=24
Add the PFX to your project. Then click once on the file, and in the Properties window, set the Build Action to Embedded Resource
Read the embedded PFX file and import the certificate
This is C# but you should be able to translate to C++/CLI pretty easily:
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MyFile.pfx");
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
var cert = new X509Certificate2(bytes, "certPassword");
This worked for me once I embedded it.
byte[] cert = Properties.Resources.nameOfCertificate;
X509Certificate2 x509 = new X509Certificate2();
x509.Import(cert, "password", X509KeyStorageFlags.MachineKeySet);

Resources