Here's my simple method:
private static X509Certificate2 GetCertificateFromStore(StoreLocation storeLocation, string certName) {
var store = new X509Store(StoreLocation.LocalMachine);
try {
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, certName, true);
return certs.Count == 0 ? null : certs[0];
}
finally {
store.Close();
}
}
Debug locals show that store.Certificates has been loaded and contains two certificates — the default "localhost" one and one I've imported, so the correct store has been successfully opened.
However, the Find() method always returns an empty result, regardless of which certificate I search for and whether I use FindBySubjectName or FindByThumbprint.
Any ideas what could be wrong? It is a simple console app created for the sole purpose of learning & testing certificate loading, i.e., virtually nothing in project configuration or anywhere else is other than default.
Try false as your third parameter to the store.Certificates.Find() method - its possible your your certificates are not valid and are being excluded.
Related
I know there are a lot of questions on this argument but I'm stuck into this for several days now so here I am.
I've got a root certificate and a client certificate. I need to replicate in a C# web API project what the command openssl verify -CAfile ca.pem client.pem does.
This is what i know for now (hope it's actually true):
Verify() method actually validate that the certificate is signed by an authority. It's like a format control. It doesn't matter which autority signed the certificate.
X509 Chain is the way to go. Add your ca certificate inside an extra store because i'm not going to install the certificate into Windows. Then build passing the client certificate. Let's the magic happens! Unfortunately I've got some problems with the configuration.
Let me be more clear with an example
private bool VerifyCertificate(X509Certificate2 client)
{
X509Chain chain = new X509Chain();
var stringCert = WebConfigurationManager.AppSettings["CACertificate"];
var byteCert = Encoding.ASCII.GetBytes(stringCert);
var authority = new X509Certificate2(byteCert);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
chain.ChainPolicy.ExtraStore.Add(authority);
// Do the preliminary validation.
if (!chain.Build(client))
return false;
return true;
}
With this example the program returns false. The build is not passed. I'm sure the problem is with the ChainPolicy properties so i tried a different configuration
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.VerificationTime = DateTime.Now;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);
But this one is not going to verify anything, in fact using my ca cert the method returns true and using another ca certificate (which i didn't use to signed my client certificate) the method also returns true.
I Searched for an OpenSSL wrapper for C# and i found it but unfortunately is based on old libraries and the repo isn't manteined anymore. Also, i would achieve my goal using just .net framework if possible.
So guys, fast recap. I want to check that only the certificate that is firmed by my ca certificate can pass the validation, all the others must be stopped.
Thanks in advance for any help
ExtraStore isn't limiting, it provides extra certificates to help complete the chain. It provides no trust data.
In order to determine if the certificate is issued by the CA that you want you need to do something like:
private static readonly X509Certificate2 s_trustedRoot = ObtainTheRoot();
private static readonly byte[] s_normalizedRoot = s_trustedRoot.RawData;
private bool VerifyCertificate(X509Certificate2 candidate)
{
X509Chain chain = new X509Chain();
// set all the things you need to set to make it build
if (!chain.Build(candidate))
return false;
// Check that the root certificate was the expected one.
X509ChainElementCollection elements = chain.ChainElements;
return elements[elements.Count - 1].Certificate.RawData.SequenceEqual(s_normalizedRoot);
}
I promoted the cert and normalized byte form of it to statics on the assumption that they don't change once the process starts. If the cert can change dynamically then you should adjust accordingly.
Find the problem.
In my case this was the right response.
private bool VerifyCertificate(X509Certificate2 client)
{
X509Chain chain = new X509Chain();
var stringCert = WebConfigurationManager.AppSettings["CACertificate"];
var byteCert = Encoding.ASCII.GetBytes(stringCert);
var authority = new X509Certificate2(byteCert);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.ExtraStore.Add(authority);
// Do the preliminary validation.
if (!chain.Build(client))
return false;
// This piece makes sure it actually matches your known root
var valid = chain.ChainElements
.Cast<X509ChainElement>()
.Any(x => x.Certificate.Thumbprint == authority.Thumbprint);
if (!valid)
return false;
return true;
}
Now, debugging the application i have some considerations:
What the .Build() method is supposed to do?
I mean, using the certificate that is signed by the ca or using the self-signed certificate the method Always returns true BUT if i use the trusted one it will add the certificate inside the chain.ChainElementsotherwise nothing is added.
I need to understand this thing but all the test I've done said the method works
All, I run into an issue where the service account my ASP.NET web forms application (.Net Framework 4.6.1) runs under cannot load the X509Certificate(.pfx) from the personal store on the windows 2012 R2 server .Here is how I imported the certificate to the certificate store
I Logged into the server using the service account(domain\username) ,used mmc snap in to import the certificate to Current User Personal Certificate Store (please see screenshot at the end)
This is the code I am using to load the certificate in C#.But the certificate
is null
public X509Certificate2 Load()
{
X509Certificate2 x509Certificate = null;
var store = new X509Store(StoreName.My,StoreLocation.CurrentUser);
string thumbPrint = StripTheSpacesAndMakeItUpper(ConfigurationManager.AppSettings["pfxthumbPrint"]);
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates;
foreach (var x509 in certCollection)
{
if (x509.Thumbprint.Equals(thumbPrint))
{
x509Certificate = x509;
break;
}
}
store.Close();
return x509Certificate;
}
private string StripTheSpacesAndMakeItUpper(string thumbPrint)
{
if(!string.IsNullOrWhiteSpace(thumbPrint))
{
return Regex.Replace(thumbPrint, #"\s|\W", "").ToUpper();
}
return thumbPrint;
}
Any suggestions on why the method Load returns null ?
]3
I don't know how you set the value of ConfigurationManager.AppSettings["pfxthumbPrint"]. I guess you double clicked the certificate in your CurrentUser store and copied the thumbprint from the Details tab, right? If that is the case, you copied also one invisible character from the beginning of the thumbprint.
The saddest thing is that this character (I don't know what it is) is not visible in your app.config/web.config. The only way to get rid of if is to delete the first quote character with the first character of the thumbprint and type them in again manually. Or delete entire thumbprint and the quotes and type them again if you wish.
Instead of
if (x509.Thumbprint.Equals(x509CertificateFriendlyName))
Shouldn't it be
if (x509.Thumbprint.Equals(thumbPrint))
...?
Also, you appear to have x509Certificate declared as a local variable and then you discard it. Did you intend to assign the value to an instance variable perhaps? I don't even see a return statement.
Also, you're not disposing your store, although that probably isn't the cause of your issue.
Here's a different version that addresses these issues, and will also eliminate any invisible characters in the configuration entry (see pepo's answer for why).
public X509Certificate2 Load()
{
var thumbPrintFromConfig = ConfigurationManager.AppSettings["pfxthumbPrint"]
var thumbPrint = Regex.Replace(thumbPrintFromConfig.ToUpper(),"[^A-F0-9]",""); //Keep only hex digits
return Load(thumbPrint);
}
private X509Certificate2 Load(string thumbPrint)
{
using (var store = new X509Store(StoreName.My,StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
var cert = store
.Certificates
.OfType<X509Certificate2>()
.Where(x => x.Thumbprint == thumbPrint)
.Single();
store.Close();
return cert;
}
}
I cannot create SQL CLR to do a decrypt function and encrypt function from cert store with getting the resultant string from a cert.
'System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.StorePermission, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=blahblahblahblah' failed.
System.Security.SecurityException: '
Is there a way to make SQL CLR have access to your cert stores in a sys config you can change or another trust setting? Do I need to load the dependent System.Security.Cryptography dependent library? I ultimately want to set this up on Azure SQL Server too so that may be a roadblock as well. I know that my decrypt and encrypt work fine when hard coding the resultant data obtained from the cert that I need for the pattern. But the access to the cert would be better to just do:
fDecrypt('mythumbprintstringtofinditlocally', 'DLQUOUIEWLKCJLAKJA=!##$'(some data that is encrypted with local cert)
rather than hard code the data in. As this cert can change for environments.
private void SetEncryptionDirectlyFromThumbprint(string certThumbprint)
{
var cert = GetCertificateByThumbprint(certThumbprint, StoreLocation.LocalMachine, StoreName.My);
}
//needs System.Security.Cryptography library.
private X509Certificate2 GetCertificateByThumbprint(string thumbprint, StoreLocation storeLocation, StoreName storeName, bool requirePrivateKey = false)
{
if (string.IsNullOrEmpty(thumbprint))
{
throw new ArgumentNullException("thumbprint");
}
var cleanedThumbprint = thumbprint.Replace(" ", "").ToUpperInvariant();
var store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var foundCerts = store.Certificates.Find(X509FindType.FindByThumbprint, cleanedThumbprint, false);
foreach (var cert in foundCerts)
{
if (!requirePrivateKey || cert.HasPrivateKey)
{
return cert;
}
}
}
finally
{
store.Close();
}
return null;
}
Looks like all it was thus far was the 'Permission level' set under Project>SQLCLR>Permission Level. I changed it to 'External_Access' and voila, it worked right away. Now the problem it seems will be that Azure only likes safe assemblies and just included the ability to use CLR recently. Arghhh, cannot win them all I guess.
I have X509 certificates that are stored on the network. I can read the chain from remote windows certificate store. I need to sign some data and include chain to the signature to make it possible to validate it later.
The problem is that I can't find a way to put certificate chain to the CsmSigner. I have read that it takes certificate from constructor parameter and tries to build a chain with X509Chain.Build. It ignores Certificates list values and fails (obviously) because no certificate can be found in the local Windows cert store.
Please find below my test code (that works only if certificates were saved locally to the windows cert store)
protected byte[] SignWithSystem(byte[] data, X509Certificate2 cert, X509Certificate[] chain)
{
ContentInfo contentInfo = new ContentInfo(data);
SignedCms signedCms = new SignedCms(contentInfo, true);
CmsSigner cmsSigner = new CmsSigner(cert);
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //sha256
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
if (chain != null)
{
//adding cert chain to signer
cmsSigner.Certificates.AddRange(chain);
signedCms.Certificates.AddRange(chain);
}
signedCms.ComputeSignature(cmsSigner); //fails here with System.Security.Cryptography.CryptographicException : A certificate chain could not be built to a trusted root authority.
byte[] signedPkcs = signedCms.Encode();
return signedPkcs;
}
Is there any way to make it work without uploading certificates to the local store? Should I use any alternative signer?
I can try to upload certificates to the store but the problems are that
I have to add and remove certificates (permissions have to be granted)
There are several processes that applies signature so cross-process synchronization have to be added.
This is not that I'd like to do.
Example CMS Signing with BouncyCastle for .NET
You could use the BouncyCastle crypto library for .NET, which contains its own X509 certificate and CMS signing machinery. A lot of the examples and documentation on the web are for Java, as BouncyCastle was a Java library first. I've used the answer to this Stackoverflow question as a starting point for the certificate and key loading, and added the CMS signing. You may have to tweak parameters to produce the results you want for your use case.
I've made the signing function look approximately like yours, but note the private key is a separate parameter now.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509.Store;
class Program
{
protected static byte[] SignWithSystem(byte[] data, AsymmetricKeyParameter key, X509Certificate cert, X509Certificate[] chain)
{
var generator = new CmsSignedDataGenerator();
// Add signing key
generator.AddSigner(
key,
cert,
"2.16.840.1.101.3.4.2.1"); // SHA256 digest ID
var storeCerts = new List<X509Certificate>();
storeCerts.Add(cert); // NOTE: Adding end certificate too
storeCerts.AddRange(chain); // I'm assuming the chain collection doesn't contain the end certificate already
// Construct a store from the collection of certificates and add to generator
var storeParams = new X509CollectionStoreParameters(storeCerts);
var certStore = X509StoreFactory.Create("CERTIFICATE/COLLECTION", storeParams);
generator.AddCertificates(certStore);
// Generate the signature
var signedData = generator.Generate(
new CmsProcessableByteArray(data),
false); // encapsulate = false for detached signature
return signedData.GetEncoded();
}
static void Main(string[] args)
{
try
{
// Load end certificate and signing key
AsymmetricKeyParameter key;
var signerCert = ReadCertFromFile(#"C:\Temp\David.p12", "pin", out key);
// Read CA cert
var caCert = ReadCertFromFile(#"C:\Temp\CA.cer");
var certChain = new X509Certificate[] { caCert };
var result = SignWithSystem(
Guid.NewGuid().ToByteArray(), // Any old data for sake of example
key,
signerCert,
certChain);
File.WriteAllBytes(#"C:\Temp\Signature.data", result);
}
catch (Exception ex)
{
Console.WriteLine("Failed : " + ex.ToString());
Console.ReadKey();
}
}
public static X509Certificate ReadCertFromFile(string strCertificatePath)
{
// Create file stream object to read certificate
using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read))
{
var parser = new X509CertificateParser();
return parser.ReadCertificate(keyStream);
}
}
// This reads a certificate from a file.
// Thanks to: http://blog.softwarecodehelp.com/2009/06/23/CodeForRetrievePublicKeyFromCertificateAndEncryptUsingCertificatePublicKeyForBothJavaC.aspx
public static X509Certificate ReadCertFromFile(string strCertificatePath, string strCertificatePassword, out AsymmetricKeyParameter key)
{
key = null;
// Create file stream object to read certificate
using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read))
{
// Read certificate using BouncyCastle component
var inputKeyStore = new Pkcs12Store();
inputKeyStore.Load(keyStream, strCertificatePassword.ToCharArray());
var keyAlias = inputKeyStore.Aliases.Cast<string>().FirstOrDefault(n => inputKeyStore.IsKeyEntry(n));
// Read Key from Aliases
if (keyAlias == null)
throw new NotImplementedException("Alias");
key = inputKeyStore.GetKey(keyAlias).Key;
//Read certificate into 509 format
return (X509Certificate)inputKeyStore.GetCertificate(keyAlias).Certificate;
}
}
}
.NET CMS (Quick-fix with rest of chain omitted from signature)
I can reproduce your problem with a certificate whose root is not in the trusted certificate store, and confirm that adding the certificate chain to the cmsSigner/signedCms Certificates collection does not avoid the A certificate chain could not be built to a trusted root authority error.
You can sign successfully by setting cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
However, if you do this, you will not get the rest of the chain in the signature. This probably isn't what you want.
As an aside, in your example you are using X509Certificate for the array of certificates in the chain, but passing them to an X509Certificate2Collection (note the "2" in there). X509Certificate2 derives from X509Certificate, but if its not actually an X509Certificate2 that you put in one of those collections, you'll get a cast error if something iterates over the collection (you don't get an error when adding a certificate of the wrong type unfortunately, because X509Certificate2Collection also derives from X509CertificateCollection and inherits its add methods).
Adding sample code that creates detached PKCS7 signature using BouncyCastle (thanks to softwariness) without Certificate store.
It uses .net X509Certificate2 instances as input parameter. First certificate in collection have to be linked with private key to sign data.
Also I'd like to note that it is not possible to read private key associated with certificate from remote Windows cert store using .net X509Certificate2.PrivateKey property. By default private key is not loaded with certificate using X509Store(#"\\remotemachine\MY", StoreLocation.LocalMachine) and when X509Certificate2.PrivateKey property is accessed on local machine it fails with error "Keyset does not exist".
public void SignWithBouncyCastle(Collection<X509Certificate2> netCertificates)
{
// first cert have to be linked with private key
var signCert = netCertificates[0];
var Cert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(signCert);
var data = Encoding.ASCII.GetBytes(Cert.SubjectDN.ToString());
var bcCertificates = netCertificates.Select(_ => Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(_)).ToList();
var x509Certs = X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(bcCertificates));
var msg = new CmsProcessableByteArray(data);
var gen = new CmsSignedDataGenerator();
var privateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signCert.PrivateKey).Private;
gen.AddSigner(privateKey, Cert, CmsSignedDataGenerator.DigestSha256);
gen.AddCertificates(x509Certs);
var signature = gen.Generate(msg, false).GetEncoded();
Trace.TraceInformation("signed");
CheckSignature(data, signature);
Trace.TraceInformation("checked");
try
{
CheckSignature(new byte[100], signature);
}
catch (CryptographicException cex)
{
Trace.TraceInformation("signature was checked for modified data '{0}'", cex.Message);
}
}
void CheckSignature(byte[] data, byte[] signature)
{
var ci = new ContentInfo(data);
SignedCms signedCms = new SignedCms(ci, true);
signedCms.Decode(signature);
foreach (X509Certificate cert in signedCms.Certificates)
Trace.TraceInformation("certificate found {0}", cert.Subject);
signedCms.CheckSignature(true);
}
To be clear, I am no security or cryptography expert.. but per my knowledge, for receiver to be able to validate the signature, the root certificate in the certificate chain you used for signing, must already be a trusted root for the receiver.
If the receiver does not have the root certificate already in their store, and marked as a trusted root... then doesn't matter how you sign the data.. it will fail validation on receiver end. And this is by design.
See more at Chain of trust
Hence the only real solution to your problem I see is to ensure that the root certificate is provisioned as trusted root on both ends... Typically done by a Certificate Authority.
Enterprise application scenario - Typically in an enterprise some group in IT department (who have access to all machines in the domain - like domain admins) would enable this scenario by ensuring that every computer in the domain has root certificate owned by this group, present on every machine as trusted root, and an application developer in the enterprise typically requests a new certificate for use with their application, which has the chain of trust going back to the root certificate already distributed to all machines in the domain.
Found out contact person for this group in your company, and have them issue a certificate you can use for signature.
Internet application scenario - There are established Certificate Authorities, who own their root certificates, and work with OS vendors to ensure that their root certificates are in trusted store, as the OS vendor ships the OS to it's customers. (One reason why using pirated OS can be harmful. It's not just about viruses / malware..). And that is why when you use a certificate issued by VeriSign to sign the data, the signature can be validated by most other machines in the world.
I have developed a self-hosted api.
The api traffic needs to run over SSL.
Using a combination of netsh commands I have managed to successfully add the certificate and then bind a route to my service. Happy Days.
But, I have to write an installer to do this programmatically.
The problem is that when I add the certificate using my c# code, I can see it the certificate MMC but when I try to bind to it I get an error of:
SSL Certificate add failed, Error: 1312
A specified log-on session does not exist. It may already have been terminated.
As I say, when I do it manually with these steps I don't get the problem...
List item
Double click on the .pfx file.
MMC opens.
I select "Local Machine"
On the next screen I confirm the .pfx file location and name.
I enter the password for the certificate and select "Include all extended properties"
On the next screen I let it default to "Automatically select the certificate store based on the type of certificate"
I then get a confirmation screen.
When I click "Finish" I get a message "The import was successful"
I can then see it in MMC under Personal > Certificates
And it lets me add the route using netsh from a command prompt - Happy Days.
When I try an do it programmatically with the following code:
public static bool ConfigureSSLCertificate(string file, string password, string method)
{
try
{
X509Certificate2 cert = new X509Certificate2(file, password);
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
if (!store.Certificates.Contains(cert))
{
if (method == "add")
{
store.Add(cert);
}
}
if (method == "remove")
{
store.Remove(cert);
}
return true;
}
catch { return false; }
}
The certificate appears in my MMC in exactly the same place but when I try and add the route with the exact same netsh command as before I get the error mentioned above:
netsh>http add sslcert ipport=0.0.0.0:8088 certhash=fb93ce2c4d8bd88c82e63e3372a050ba84f15e94 appid={bb14356a-a14f-4589-82ce-b80d38b8741e}
For some reason, when I add the certificate manually using the MMC and when I run my code something is different. Something that is stopping the route being added.
The solution is actually simple - I too have struggled with this, and have now found the solution. How can a manually added certificate differ from the programatically added one? Well, the short answer is to change your certificate load line to this:
X509Certificate2 cert = new X509Certificate2(file, password, X509KeyStorageFlags.MachineKeySet);
The key being that last parameter, which tells the certificate to save the private key stored in the machine location, and not the user location. Then the netsh command can find the private key, and can work.
The solution was found in the explanatory text by Paul Stovell and some digging to see how I could set that flag when loading the certificate into the store.
Now, why I can't programmatically do the netsh function is another matter...
I think I have fixed it.
there was a problem with the .pfx I had been trying to install. I have no idea why. what fixed it for me was exporting a working certificate from my personal store with all options set to true and then run it in the following:
public static bool ServiceInstall(string serviceName, string serviceDescription, string executablePath)
{
try
{
ServiceProcessInstaller ProcesServiceInstaller = new ServiceProcessInstaller();
ProcesServiceInstaller.Account = ServiceAccount.LocalSystem;
ServiceInstaller ServiceInstallerObj = new ServiceInstaller();
InstallContext Context = new System.Configuration.Install.InstallContext();
String path = String.Format("/assemblypath={0}", executablePath);
String[] cmdline = { path };
Context = new System.Configuration.Install.InstallContext("", cmdline);
ServiceInstallerObj.Context = Context;
ServiceInstallerObj.DisplayName = serviceName;
ServiceInstallerObj.Description = serviceDescription;
ServiceInstallerObj.ServiceName = serviceName;
ServiceInstallerObj.StartType = ServiceStartMode.Automatic;
ServiceInstallerObj.Parent = ProcesServiceInstaller;
System.Collections.Specialized.ListDictionary state = new System.Collections.Specialized.ListDictionary();
ServiceInstallerObj.Install(state);
return true;
}
catch
{
return false;
}
}
and then use that pfx file
I have no idea why the old pfx worked from the command line but didn't work from code.
HTH