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 ...
I'm developing an application in UWP which acts as a server and i made a self-signed certificate and installed it on both the server and client, I checked the certificate if it has a private key and it does, and when I retrieve it from the store programmatically it loads the private key, but for some reason when the authentication handshake starts the server says that "the credentials supplied to the package were not recognized", the only way it works is when I get the certificate as a PFX from a folder...
this one works:
Selectedcert = new X509Certificate2("LocalCertificate.pfx","password", X509KeyStorageFlags.UserKeySet);
this doesn't:
X509Store store = new X509Store(StoreName.Root,StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection cers =
store.Certificates.Find(X509FindType.FindByIssuerName, "localhost", false);
X509Certificate2 Selectedcert = null;
foreach (var c in cers)
if (c.HasPrivateKey && c.PrivateKey != null)
Selectedcert = c;
I checked the manifest and the application has Capabilities to the Shared User Certificates also I checked the certificate permissions and the user does have permissions to read the private-key
Edit:
also i have Tried both StoreName.My and StoreName.Root
i tried the same code in a console application and the authentication handshake is ok, so the problem is with the UWP restrictions, it doesn't let the application to use the private key or something like that
i dont know how to get an X509Cert in UWP if someone knows please inform me, thank you :)
I think the issue is that UWP apps are running in the sandbox so that it has limitations when trying to accessing the localhost. So in your scenario, you could not get the certificates.
Update:
The sharedUserCertificates works for UWP APIs like UserCertificateStore Class and CertificateStores Class. You could check the official sample -UserCertificateStore to see how these APIs could get the certificates from the certificates store.
After deploying a azuze container service and using swarm, how do one connect using the example given:
var credentials = new CertificateCredentials (new X509Certificate2 ("CertFile", "Password"));
var config = new DockerClientConfiguration("http://ubuntu-docker.cloudapp.net:4243", credentials);
DockerClient client = config.CreateClient();
I have made the certificate and just cant figure out wht the proper endpoint to use is?
the url from azure portl: <name>-mgmt.<region>.cloudapp.azure.com
ACS does not use certs by default. We use SSH tunneling as documented at https://learn.microsoft.com/en-us/azure/container-service/container-service-connect
If you have connected to the masters and manually configured it to use certs as well as correctly installing those certs o the masters then there is nothing magical about the endpoints and connection details. It's just Docker, so follow the appropriate Docker documentation. The correct URL is, as you note in your question <name>-mgmt.<region>.cloudapp.azure.com.
However, you should be aware that since we do not use certs by default we do not open the necessary ports on the Master LB. You will also need to open those on your master LB. For an example (which is against the agent LB but the processes is the same) see https://learn.microsoft.com/en-us/azure/container-service/container-service-enable-public-access
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();
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.