Custom Certificate Store with generated X509Certificate2 in gRPC - c#

I am attempting to create a gRPC server and client using ssl (with .NET 5 and VS2019).
I want to use a generated X509Certificate2 as a root certificate to generate other client certificates. For that, I wrote a helper class CertificateUtil, following these threads:
How can I create a self-signed certificate using C#?
Generate and Sign Certificate Request using pure .net Framework.
Next, the root certificate should be registered as a custom trust store in the startup settings of the gRPC server, and the client should connect using the generated client certificate.
I have the following question:
Is it possible to register a custom trust store in gRPC?
If not, what is a good alternative?
If yes, what part of the process I explain below is incorrect?
Currently, I am getting the following errors:
client: "Error starting gRPC call. HttpRequestException: The SSL connection could not be established, see inner exception. IOException: Received an unexpected EOF or 0 bytes from the transport stream."
server: "The local security authority (LSA) is unreachable"
Steps to reproduce:
Pull the following MWE: https://github.com/Renopph/GrpcServerClient
Uncomment lines 10 and 11 in GprcCert/Program.cs and run. This should create two certificate files, GrpcServer.pfx and GrpcClient.pfx. Set both files' properties to Copy always. Do NOT register these certificates in your system's trust store.
Place GrpcClient.pfx in the root of the GrpcClient project.
Comment out lines 10 and 11, and uncomment line 12 in GprcCert/Program.cs.
Right click the Solution, open Properties. Select "Multiple startup projects" and set both GrpcCertand GrpcClient to "Start". Then run the solution (should run GrpcCert first, then GrpcClient).
The client and server both show the aforementioned errors.
I also tried leaving out the KestrelServerOptions in the Startup.cs of the server. This allowed any client to connect, even without the certificate.

I will write up this answer, but as I already said I think it only answers half your questions. Regarding your question Is it possible to register a custom trust store in gRPC? I think the answer is yes, as long as you fulfill the TLS requirements of gRPC and the underlying certificate structure works, it should be possible. According to the MS Documentation for certificate authentication, the certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, therefore the Kestrel (which hosts the gRPC services) does not care if the (root) certificate comes from a custom trust store or the local machine store (or somewhere else).
It took me a while to get both my self-signed certs in the local machine store and my company's certs in the Trusted Root Certification Authorities Certificate Store to work with gRPC, therefore it's maybe easier to get your certificates first to a point where they just work with the root cert getting fetched from the local machine store, and then move it to a custom store.
This is the service I used to inject the certs on the client- and serverside (in .NET 6), I think you could extend it easily to fetch a cert from any other location (like a custom certificate store):
using System.Security.Cryptography.X509Certificates;
namespace Shared.Certificates
{
public class CertificateService : ICertificateService
{
public X509Certificate2 GetCertificateFromLocalMachineStore(string friendlyName)
{
var store = GetLocalMachineCertificates();
X509Certificate2 certificate = null;
foreach (var cert in store.Cast<X509Certificate2>().Where(cert => cert.FriendlyName.Equals(friendlyName)))
{
certificate = cert;
}
return certificate;
}
private static X509Certificate2Collection GetLocalMachineCertificates()
{
var localMachineStore = new X509Store(StoreLocation.LocalMachine);
localMachineStore.Open(OpenFlags.ReadOnly);
var certificates = localMachineStore.Certificates;
localMachineStore.Close();
return certificates;
}
}
}
And the interface for the service, which you could extend for a method like public X509Certificate2 GetCertificateFromCustomTrustStore(string friendlyName), where you could fetch your cert from wherever you want to store them:
using System.Security.Cryptography.X509Certificates;
namespace Shared.Certificates
{
public interface ICertificateService
{
X509Certificate2 GetCertificateFromLocalMachineStore(string friendlyName);
}
}
Server-side certificate injection with the service from above:
CertificateService service = new CertificateService();
X509Certificate2 cert = service.GetCertificateFromLocalMachineStore("grpc_cert");
builder.WebHost.ConfigureKestrel(opt =>
{
opt.ConfigureHttpsDefaults(h =>
{
h.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.AllowCertificate;
h.CheckCertificateRevocation = false;
h.ServerCertificate = cert;
});
}
And client-side certificate injection with the service from above:
CertificateService service = new CertificateService();
X509Certificate2 cert = service.GetCertificateFromLocalMachineStore("grpc_cert");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);
Channel = GrpcChannel.ForAddress($"https://{address}:{port}", new GrpcChannelOptions
{
HttpHandler = handler
});
If you want to, I can also provide you with the script that I used to generate my self-signed certificates, but I don't think they are of much use to you, since you have different certificates. Sadly I cannot help you more, I think your main problem is somewhere in your certificate structure, and I don't know too much about certs ...

Related

Azure.Identity.CredentialUnavailableException GetCertificate from AzureKeyVault using azure.Security.KeyVault.Certificates

I have an app which uses certificates ( Uploaded the cert) in Azure Azure KeyVault.
I am trying to use asp.net application 4.7 to connect to key vault and retrieve the application Certificate and the Secret associate with it
Previously I used Microsoft.Azure.KeyVault and KeyVaultClient in combination with GetCertificateAsync and GetSecretAsync to pull the certificate and its secret.
However I recognized that Microsoft.Azure.KeyVault is deprecated so i researched and I found That I should use Azure.Security.KeyVault.Certificates instead.
I wrote the below code
New Way:
using Azure.Security.KeyVault.Certificates;
public static X509Certificate2 GetCertificateFromVault(string keyVaultName, string certificateName)
{
var keyVaultUri = $"https://{keyVaultName}.vault.azure.net/";
var client = new CertificateClient(new Uri(keyVaultUri), new DefaultAzureCredential());
KeyVaultCertificateWithPolicy keyVaultCertificateWithPolicy = client.GetCertificate(certificateName);
return new X509Certificate2(keyVaultCertificateWithPolicy.Cer);
}
However I get error on calling client.GetCertificate(certificateName); in above code which is my new way
Here is the error
Multiple exceptions were encountered while attempting to authenticate. ---> Azure.Identity.CredentialUnavailableException: E
nvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information.
https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex, String additionalMessage)\r\n at
Azure.Identity.EnvironmentCredential.d__12.MoveNext()\r\n
I have KeyVaultCertificateOfficer role and I can retrieve the certificates using my old way on the local machine so I am sure I do not have access issue.
I looked at developer guide it says that I need to create
environment variables for AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID
Is that the solution? I do not understand why? Do I need to do this on all my different environment like UAT and PROD and create these variables. I kind of dont like that so i think i missing something here.
If I absolutely need to create env variables also not sure what to use as
AZURE_CLIENT_SECRET as I use Certificate not secret for my App. I saw AZURE_CLIENT_CERTIFICATE_PATH but that is the path to pfx in local machine right which we do not want that.
Maybe my understanding is totally wrong I am not sure. So any guidance would be much appreciated!
Create a key Vault and certificate in azure
And we have chosen the same .Net4.7 Framework
Used the NuGet package "Azure.Security.KeyVault.Certificates
The below namspaces are used in the application
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using System.Security.Cryptography.X509Certificates;
Below is the code used for fetching the certificates from azure
string certificateName = "TestCertificate";
var KeyVaultUri = #"https://KeyVaultName.vault.azure.net/";
var client = new CertificateClient(new Uri(KeyVaultUri), new DefaultAzureCredential());
KeyVaultCertificateWithPolicy keyVaultCertificateWithPolicy = client.GetCertificate(certificateName);
var res= new X509Certificate2(keyVaultCertificateWithPolicy.Cer);
Certificate details are fetched in below screens

C#: How to invoke a SOAP service requiring client-side authentication with certificates installed at runtime

I have an application deployed to IIS that needs to invoke a SOAP service. It's using WCF from .NET Framework. That SOAP service requires that requests made be authenticated with a client-side certificate which is given at runtime. Admin users of the application can update the used certificate in a back-office. The goal is for autonomy and the certificate lifecycle management be independent from IIS or the underlying system so using the machine certificate store is not an option.
Here's the initial code:
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
var client = new ServiceReference1.myClient(binding, new EndpointAddress(serviceUrl));
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var certificate = new X509Certificate2(certificateBinary, certificatePassword);
client.ClientCredentials.ClientCertificate.Certificate = certificate;
//use the client
var result = client.myMethod(new ServiceReference1.MethodRequest());
certificateBinary is the result of loading a PFX file containing the full certificate chain (client certificate, intermediate and root CAs) and certificatePassword the password used to create that file.
But the request is rejected by the server. From looking at Wireshark, it seems only the client-certificate is sent. This is different from what happens if we install the PFX on the machine store which works fine.
So the next step I tried was to install the certificates at runtime. First load them:
X509Certificate2Collection collection = new X509Certificate2Collection();
try {
collection.Import(ssCertificateFile, ssPassword, X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
}
Then identify what kind of certificates they are and finally installing them on the current user store:
private static void InstallCertificates(X509Certificate2Collection clientCerts, X509Certificate2Collection intermediateCAs, X509Certificate2Collection RootCAs) {
using (X509Store personalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser)) {
personalStore.Open(OpenFlags.ReadWrite);
personalStore.AddRange(clientCerts);
}
using (X509Store intermediateStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser)) {
intermediateStore.Open(OpenFlags.ReadWrite);
intermediateStore.AddRange(intermediateCAs);
}
using (X509Store trustedCAsStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
trustedCAsStore.Open(OpenFlags.ReadWrite);
trustedCAsStore.AddRange(rootCAs);
}
}
This fails when installing the root CAs in trusted root certificate authorities (StoreName.Root) with:
System.Security.Cryptography.CryptographicException: The request is not supported.
at System.Security.Cryptography.X509Certificates.X509Store.Add(X509Certificate2 certificate)
at System.Security.Cryptography.X509Certificates.X509Store.AddRange(X509Certificate2Collection certificates)
at OutSystems.NssCertificationExtremeXP.CssCertificationExtremeXP.InstallCertificates(CertificatesClassifier certificates)
so only the client certificate and the intermediate CAs get installed and at runtime apparently this is not enough.
But if I take the exact same code as it is and run it with in a separate C# project, when installing the root CAs there's a confirmation dialog
and if I click OK the certificate is installed.
From here and here, it looks like every time we want to install something in the user Trusted Root Certificate Authorities, that prompt happens and it probably is not supported on the context of a non-GUI usage.
The problem is that even if I don't install the root CA in the store, I can successfully call the SOAP service when running this stand-alone app, only when running under IIS this fails.
Does anyone know why this happens and how to solve it?

Dynamically adding a website with an HTTPS binding pointing to a dynamically generated self-signed certificate

I'm currently attempting to dynamically add a new website using the Microsoft.Web.Administration library provided by IIS7+. This is going to be a part of an installation process in which a self-signed certificate needs to be added and bound to the HTTPS binding of this website.
My research took me to this post on StackOverflow which makes use of the BouncyCastle API: Generate self signed certificate on the fly. I have tried to replicate the functionality provided by IIS administration tool for creating such a certificate by making slight alterations to the code. I have modified the signature algorithm in both methods from SHA256WithRSA to SHA1WithRSA. I am also passing the SubjectName and IssuerName as the local machine's name using Dns.GetHostEntry("127.0.0.1").HostName. I'm also setting the FriendlyName property of the X509Certificate2 object. The final modification involved passing the sl parameter of the addCertToStore method as StoreLocation.LocalMachine.
The following is a snippet of the code for dynamically creating the website and adding an HTTPS binding passing the cert.GetCertHash() data as a parameter to the Site.Bindings.Add(...) method which takes in the Binding Information, Certificate Hash and the Certificate Store Name (see MSDN documentation):
using (ServerManager iisManager = new ServerManager())
{
Site testSite = iisManager.Sites.Add(siteName, targetDir, sitePortNumber);
testSite.Bindings.Add("*:" + sitePortNumber + ":", certificateHash, "MY");
iisManager.CommitChanges();
}
The issue is that when attempting to commit the changes in IIS I get the following COMException:
A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)
Checking what happened in IIS it seems that the certificate is present but it is not being mapped correctly:
In the image linked above, the topmost certificate is the one being added dynamically i.e. the one mentioned in my post. The last one is a sample self-signed certificate created through the IIS administration tool.
The image linked above shows that the mapping between the HTTPS binding and the newly created SSL certificate failed.
Personal Certificates under Local Computer -> Personal:
This image contains a list of personal certificates which are present in certificate manager under the Local Computer section. The top one is the dynamically generated one whilst the bottom one was created using the IIS administration tool.
Above are the certificate details of the self-signed certificates created using the IIS administration tool (left hand side) and the dynamically generated one (right hand side).
Some of the most notable differences between the two of them are:
Missing Key Usage and Enhanced Key Usage details in the dynamically generated one.
The certificate status and the icon indicating that there is something wrong with it.
So the question is what is wrong with the code involving the creation of the self-signed certificate which is causing the mapping to the new IIS website to fail?
Thanks
Moved the SSL certificate from current user to local machine and it worked.
Here is my code to add the certificate.
I also noticed X509KeyStorageFlags.MachineKeySet was the only flag that did not produce the error.
string certPath = "c:/test2/certName.p12";
string certPass = "TestPassword";
// Create a collection object and populate it using the PFX file
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import(certPath, certPass, X509KeyStorageFlags.MachineKeySet);
foreach (X509Certificate2 cert in collection)
{
if(cert.Subject.Equals("CN=TestServer, O=Test"))
{
X509Store store1 = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store1.Open(OpenFlags.ReadWrite);
store1.Add(cert);
store1.Close();
}
if (cert.Subject.Equals("CN=TestClient, OU=Applications, O=Test"))
{
X509Store store1 = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store1.Open(OpenFlags.ReadWrite);
store1.Add(cert);
store1.Close();
}
}
And here is my code to create the binding
ServerManager serverManager = new ServerManager();
var site = serverManager.Sites["Default Web Site"];
site.Bindings.Add("*:443:TestClient", certificate[0].GetCertHash(), "MY");
// site.Bindings.Add("*:443:TestClient", "https");
serverManager.CommitChanges();

"The credentials supplied to the package were not recognized" error when authenticating as server with certificate generated using BouncyCastle

I'm trying to create a certificate using the BouncyCastle.Crypto dll, which is then used to authenticate a SslStream as the server in a Windows Service process, which runs under the Local System account.
However when I get to the SslStream.AuthenticateAsServer(certificate) call, it throws a Win32 exception with the error message "The credentials supplied to the package were not recognized".
There are several questions on here about this error message, but none of them seem to describe, or solve, my particular problem.
In the hope that someone may be able to offer some help, I include the code I am using to create and install the certificate:
// First create a certificate using the BouncyCastle classes
BigInteger serialNumber = BigInteger.ProbablePrime(120, new Random());
AsymmetricCipherKeyPair keyPair = GenerateKeyPair();
X509V1CertificateGenerator generator = new X509V1CertificateGenerator();
generator.SetSerialNumber(serialNumber);
generator.SetIssuerDN(new X509Name("CN=My Issuer"));
generator.SetNotBefore(DateTime.Today);
generator.SetNotAfter(DateTime.Today.AddYears(100));
generator.SetSubjectDN(new X509Name("CN=My Issuer"));
generator.SetPublicKey(keyPair.Public);
generator.SetSignatureAlgorithm("SHA1WITHRSA");
Org.BouncyCastle.X509.X509Certificate cert = generator.Generate(
keyPair.Private, SecureRandom.GetInstance("SHA1PRNG"));
// Ok, now we have a BouncyCastle certificate, we need to convert it to the
// System.Security.Cryptography class, by writing it out to disk and reloading
X509Certificate2 dotNetCert;
string tempStorePassword = "Password01"; // In real life I'd use a random password
FileInfo tempStoreFile = new FileInfo(Path.GetTempFileName());
try
{
Pkcs12Store newStore = new Pkcs12Store();
X509CertificateEntry entry = new X509CertificateEntry(cert);
newStore.SetCertificateEntry(Environment.MachineName, entry);
newStore.SetKeyEntry(
Environment.MachineName,
new AsymmetricKeyEntry(keyPair.Private),
new [] { entry });
using (FileStream s = tempStoreFile.Create())
{
newStore.Save(s,
tempStorePassword.ToCharArray(),
new SecureRandom(new CryptoApiRandomGenerator()));
}
// Reload the certificate from disk
dotNetCert = new X509Certificate2(tempStoreFile.FullName, tempStorePassword);
}
finally
{
tempStoreFile.Delete();
}
// Now install it into the required certificate stores
X509Store targetStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
targetStore.Open(OpenFlags.ReadWrite);
targetStore.Add(dotNetCert);
targetStore.Close();
Ok, now I have created and installed the certificate. I then configure my Windows Service to use this certificate by supplying it with the generated certificate's thumbprint. I then use the certificate like this:
// First load the certificate
X509Certificate2 certificate = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 certInStore in store.Certificates)
{
if (certInStore.Thumbprint == "...value not shown...")
{
certificate = certInStore;
break;
}
}
SslStream sslStream = new SslStream(new NetworkStream(socket, false), false);
// Now this line throws a Win32Exception
// "The credentials supplied to the package were not recognized"
sslStream.AuthenticateAsServer(certificate);
Does anyone have any idea what the problem could be here?
I don't get the problem if I install a certificate created with 'makecert', but that isn't suitable for production certificates.
I've also tried creating a separate x509v1 CA certificate and then x509v3 certificate for server authentication, but I get the same error, so I removed this in the example code for simplicity.
That particular error message rings a bell. I'll guess that either you did not store the private key with the certificate, or, the Windows service does not have access to the private key. To check this, open the Certificates MMC snap-in:
Run mmc (e.g. from the Start menu)
File menu > Add/Remove Snap-in
Select "Certificates" in left pane and then click Add
Select "Computer Account" (for LocalMachine) then click Next,
and then Finish
Navigate to the certificate and double-click in the right pane. On the General tab that comes up, you should see a little key icon at the bottom, along with the text, "You have a private key that corresponds to this certificate." If not, that's the problem. The private key was not saved.
If the private key is present, click Ok to dismiss this dialog, and then right-click on the certificate in the right pane and select on the pop-up menu: All Tasks > Manage Private Keys. In that dialog, make sure that the Windows account that the service runs under has read access to the private key. If it doesn't, that's the problem.
Edit: Oops, you wrote that the service runs as Local System, so it must be a missing private key, if it is one of these two problems. I'll leave the key access check in my answer anyway, for anybody else that hits this and is not running as Local System.
Sometime the problem happens when the application try to reach the certificate doesn't have enough privilege to access the certificate, the issue may resolve by running the application as administrator.
I've the same issue, tried everything from many posts, and google researching.
But looks like I found fix.
When I changed Identify from ApplicationPoolIdentity to LocalSystem everything start working perfectly.
May be will be helpful for someone.
For me works on Windows Server 2012 R2 (.net 4.6.1) - "All Tasks > Manage Private Keys" and set access to Everyone (setting to IS_IUSRS was not enough)
Found this solution online but I can't find the source to give the credit.
Since I ran into the "The credentials supplied to the package were not recognized" problem with AuthenticateAsClient() (for client verification), I'd like to document how I solved it. It's a different method with the same end goal. Since it might be useful for AuthenticateAsServer(), figured why not.
Here I convert a BC Certificate to a .NET certificate. Add an extra step in converting it to a .NET X509Certificate2 to store it's PrivateKey property.
Org.BouncyCastle.X509.X509Certificate bcCert;
X509Certificate dotNetCert = DotNetUtilities.ToX509Certificate(bcCert);
X509Certificate2 dotNetCert2 = new X509Certificate2(dotNetCert);
Problem showed up when adding a BouncyCastle private key to a .NET private key. The X509 certificates converted fine but not the private keys. I converted the BC private key to RSACryptoServiceProvider using the provided DotNetUtilities. Unfortunately it looks like the conversion isn't complete. So I created another RSACryptoServiceProvider which I then initialized. Then I imported the private key into the one I created.
// Apparently, using DotNetUtilities to convert the private key is a little iffy. Have to do some init up front.
RSACryptoServiceProvider tempRcsp = (RSACryptoServiceProvider)DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)ackp.Private);
RSACryptoServiceProvider rcsp = new RSACryptoServiceProvider(new CspParameters(1, "Microsoft Strong Cryptographic Provider",
new Guid().ToString(),
new CryptoKeySecurity(), null));
rcsp.ImportCspBlob(tempRcsp.ExportCspBlob(true));
dotNetCert2.PrivateKey = rcsp;
After that, I was able to save the X509Certificate2 object directly to the key store. I didn't need the actual file so I skipped that step.
Previously, every time I have run into this issue, I have had to delete the cert out of my local machine cert store and re-import it. Then it all seems happy. I can't see how it could be a global permissions issue or invalid cert if simply re-importing it fixes the issue.
How I finally fixed it was using the winhttpcertcfg tool from the Windows Resource Kit to grant permission to the specific user that was using the cert.
The syntax would be:
"C:\Program Files (x86)\Windows Resource Kits\Tools\winhttpcertcfg" -i cert.p12 -c LOCAL_MACHINE\My -a UserWhoUsesTheCert -p passwordforp12
I had the similar issue when calling a WCF REST service from .NET application where I need to attach the client certificate; All I had to do was provide access to the certificate in cert store[mmc console] to the "NETWORKSERVICE] off course my IIS Pool was default pool which indicates its using NETWORKService user account.
the mistake that I did was, I copied the cert from another store to Local
Machine -> Personnel store where the certificate was protected with password. should import the certificate explicitly in required store.
If you running from IIS, ensure that the Application Pool has 'Load User Profile' set to true.
This was the only solution for me.
I don't recall this error but the certificate you're creating is not a valid to be used for SSL/TLS, including:
v1 (not v3) certificate;
missing extensions;
invalid CN;
...
There are several RFC that talks about this, including RFC5246 on TLS (1.2).
Finally making your own certificates is not more suitable than using the ones made by makecert (but the last one can generate the minimum set to be usable for an SSL/TLS server certificate).
I strongly suggest you to buy, from a good known Certificate Authority (CA), a SSL/TLS certificate for production. That will get you a working certificate recognized by the most browsers and tools.
Another reason for this error is that you ran the application / server under an account which password has changed, breaking its capability of accessing the certificate it wants to use in the certificate store.
This especially may not be as obvious if you use a NuGet package like LettuceEncrypt which automatically stores the LetsEncrypt in your store.
Delete the certificate from your store and reimport it.

Why do I have to add the server SSL certificate if my client program is in Java and not when it is in C#?

If I use HttpsURLConnection in a Java program and try to open an URL starting with https:// I'll get an error message:
unable to find valid certification path to requested target
and the solution I found is to add the server certificate to the client certificate storage. But if I write a C# program that uses HttpWebRequest then I don't have to add anything anywhere.
So for me it looks like a C# client "just works" and a Java client only works after being tweaked with a hammer.
Why is an extra step required for a Java client? Can I somehow skip saving the certificate to the client storage of JVM?
HttpWebRequest will use Window's own certificate store to validate certificates, i.e. the same as IE. If your IE can validate the certificate correctly, either by having the certificate or a CA path back to a trusted root, then HttpWebRequest should accept the certificate OK.
In the Java case I suspect adding the server certificate itself is wrong, unless it's self-signed in which case you'll have no choice. You should add the CA path back to a trusted root instead - you can probably pull these certificates out of Windows's CA store or download them from the root CA's website if you need them.
I believe it is because C# uses the same HTTP client as MSIE, so it has a lot of pre-installed SSL certificates including one that your use. JVM has less certificates pre-installed.
By default, Java uses its own set of trust anchors (in the default truststore, see the JSSE Reference Guide).
If you want to use the Windows certificate store, you can use the Windows-ROOT keystore as a trust store.
A good source of information on this topic is the Leveraging Security in the Native Platform Using Java SE 6 Technology and Java Secure Socket Extension (JSSE) Reference Guide on the Oracle site.
If you want Java to use the Windows certificate store to validate certificates then you can specify the the following system properties on launch:
-Djavax.net.ssl.keyStoreType=Windows-MY -Djavax.net.ssl.trustStoreType=Windows-ROOT
If you want only one connection to use the Windows certificate store to validate certificates you can modify the following code to fit your needs:
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
KeyStore ts = KeyStore.getInstance("Windows-ROOT");
ts.load(null, null);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, new char[0]);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
URL url = new URL("https://some.web.site.org");
javax.net.ssl.HttpsURLConnection urlConnection =
(javax.net.ssl.HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(ctx.getSocketFactory());
urlConnection.connect();
try (InputStream in = urlConnection.getInputStream();) {
byte[] chunk = new byte[1024];
for (int len; (len = in.read(chunk)) > -1;) {
System.out.write(chunk, 0, len);
}
} finally {
urlConnection.disconnect();
}

Categories