Loading X509Certificate2 certificate chain from store - c#

I have a file (.p12) that contains 3 certificates (chained together) password-protected, that i have installed on my store.
I'm trying to load them to my code.
The way I load them from the file is like this:
var clientCert = new X509Certificate2(#"myfile.p12", "mypassword");
How can i achieve the same result while loading them from the store?
I've tried:
var computerCaStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
computerCaStore.Open(OpenFlags.ReadOnly);
var certificates = computerCaStore.Certificates.OfType<X509Certificate2>().ToLi‌​st();
var certFromStore = certificates.Single(c => c.Thumbprint == thumbprintMerchant);
var newCert = new X509Certificate2(certFromStore.RawData, "mypassword");

certFromStore should be equivalent to clientCert, the last line is what's breaking you.
The RawData property on X509Certificate2 returns the DER-encoded value for the certificate, not the original file bytes. A certificate does not have a private key, so the last line strips it away. Your question had previously mentioned a TLS exception, and that is because your cert no longer has a private key.
If certFromStore.HasPrivateKey is false, then whatever you did to put the certificate into the store didn't work the way you think it did. It's pretty unusual for a certificate with a private key to be in the Root store.

Related

HttpClient with X509Certificate2 and ECDsa failing (server response "No required SSL certificate was sent")

Update
It turned out, that we have a problem with the SslStream (on which the HttpClient is based on). In the current version (7.xx) the certificate chain is not submitted to the server, when sent via the client. This is a known issue and discussed here and here.
I will leave this post online, since the code below might be helpful to others (it does not cause problems if you want to use the client certificate only in your requests).
I have spent a lot of time trying to find our what's wrong with the Client Certificate authentication using ECDsa based certificates with the native HttpClient of .Net Core (version 7.0.100 but also tried v.6xxx) but never got this thing running. (Btw. I used the same approach for RSA based Client Certificates without any problems).
Due to security reasons, I MUST use ECDsa client certificate + chain.
I can not understand or find information why this is not working / supported and the results are confusing to me.
When loading the certificate and the key and using them to sign and verify some data, all tests pass (see code).
In the end, the required Client Certificates are not sent to the server, resulting in an SSL exception (tested the same certificates with a Python script to verify that they are correct, there I had zero problems).
I can not imagine (at least I hope not) that these kind of Client Certificates are not supported. I would greatly appreciate any help or hints for alternative workarounds. It would be quite terrible to switch to some different language at this point :/
Reference Code
Contains a test certificate and key to play around with. The chain is missing in this example and can simply be attached to the certificate string
Part of the tests are taken from: Use X509Certificate2 to sign and validate ECDSA-SHA256 signatures
[Test]
// Just some test to better understand whether the certificate and key is working and belong together
public void TestEcdsaFunctionality()
{
var ecdsaCertificate = #"-----BEGIN CERTIFICATE-----
MIIBgzCCASoCCQD+iUUbrH+BOzAKBggqhkjOPQQDAjBKMQswCQYDVQQGEwJQRDEL
MAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNVBAoMBkFueU9yZzEL
MAkGA1UEAwwCNDIwHhcNMjIxMTIxMTE0MjExWhcNMjMxMTIxMTE0MjExWjBKMQsw
CQYDVQQGEwJQRDELMAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNV
BAoMBkFueU9yZzELMAkGA1UEAwwCNDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAT6vBU2iIcESep8UeQhfNFgfTArFYvtb2Pmlbk1+R9gdNaWEg1UK7dlt3/mH/X3
Mrg80JaTY3OPM92MY9e9gs7ZMAoGCCqGSM49BAMCA0cAMEQCIA3p2mMOYqGEzReY
br7nYLsLdF0+dV6iZSZaG1iMHwblAiA5UaJlVr5CsCkG+j1ZJEICSOnVMyx4DjA5
oZuoMYa42w==
-----END CERTIFICATE-----";
var ecdsaPrivateKey = #"MDECAQEEIM6BExC2G7P1KpViQmZ/Z65nukv8yQmvw6PqGGQcKn9boAoGCCqGSM49
AwEH";
var cert = X509Certificate2.CreateFromPem(ecdsaCertificate.ToCharArray());
var key = ECDsa.Create("ECDsa");
var keybytes = Convert.FromBase64String(ecdsaPrivateKey);
key.ImportECPrivateKey(keybytes, out _);
var helloBytes = Encoding.UTF8.GetBytes("Hello World");
// Sign data with the ECDsa key
var signed = key.SignData(helloBytes, 0, helloBytes.Count(), HashAlgorithmName.SHA256);
// Verify the data signature with the certificates public key
var verified = cert.GetECDsaPublicKey().VerifyData(helloBytes, signed, HashAlgorithmName.SHA256);
// Assume that everything went well and the data signature is valid
Assert.That(verified, Is.EqualTo(true));
// Additional tests with the X509Certificate2 object type
X509Certificate2 keyCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, key);
// Sing using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPrivateKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
signed = ecdsa.SignData(helloBytes, HashAlgorithmName.SHA256);
}
// Verify signed data using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPublicKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must be an ECDSA cert", nameof(cert));
Assert.That(ecdsa.VerifyData(helloBytes, signed, HashAlgorithmName.SHA256), Is.EqualTo(true));
}
WorkshopRegistration(keyCert);
}
// This would be what I really want to use the client certificate for
private void WorkshopRegistration(X509Certificate2 clientCert)
{
var payload = "{somepayload}";
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(clientCert);
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var result = client.PutAsync("https://someHostname.com/registration",
content).GetAwaiter().GetResult();
if (result.StatusCode != HttpStatusCode.OK)
throw new SuccessException("Registration failed with conent: " + result.Content.ToString());
}

Get encrypted data from HTTPS request to use certificate manually C#

I am trying for my project this:
I want to download the (root) certificate from given url (or from diff location in later stages)
i want then get data from given url and use certificate i download in step one to "decrypt them"
check response on given url if step 1 and 2 get me same results as just response from the server
Basically I am trying to create something that check that given certificates works on given url same as the one automatically given.
Preferably do all 3 steps in one request (so if given url has counter for access, whole procedure is just one access on website)
I can do quite nice step 1)
var client = new TcpClient(address.ToString(), 443);
var certValidation = new RemoteCertificateValidationCallback(delegate (object snd, X509Certificate certificate, X509Chain chainLocal, SslPolicyErrors sslPolicyErrors)
{
return true; //Accept every certificate, even if it's invalid
});
// Create an SSL stream and takeover client's stream
using (var sslStream = new SslStream(client.GetStream(), true, certValidation))
{
sslStream.AuthenticateAsClient(InputWWW.Text);
var serverCertificate = sslStream.RemoteCertificate;
cert = new X509Certificate2(serverCertificate);
}
But I am not able to find any tips hot to get that raw data locally.
So far I found only something like this, where I use certificate in request handler
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificates.Add(certificate);
which is usage of certificate.
I want something like this:
var binary/StringBlob = webRequest.getRawData(url,port);
var serverResponseManualy = applyCertificate/publicKey(binary/StringBlob, X509Certificate2 certificate );
checkBodyEquals(serverResponseManually, webRequest.GetResponse());
Do you know how to do it, or what should I search for?
Is it possible to do all this in one request to server or not?
Thanks
So i was able to get more info about https, after that i discover that the way i ask this question is not correct. Because certificate (asymmetric cryptography) is used only to create symmetric key, which is used for encryption/description. So encrypted message by private key cannot be obtained

The private key is not present in the X.509 certificate from code bu available in certificate manager

So I am trying to use certificates to establish communication between my solution and a SOAP based service.
I have their certificate and my certificate installed into the certification store.
I have made sure that my certificate has a private key that corresponds to it:
Certificate has private key corresponding to it
However when loading my certificate in my code from the store I get the error "The private key is not present in the X.509 certificate."
I printed the following and can see that the Private Key is empty and ContainsPrivateKey == False
ClientCertificatePublicKey: System.Security.Cryptography.X509Certificates.PublicKey, ClientCertificatePrivateKey: , ContainsPrivateKey False
So far I have tried:
Setting the keystorageflags to:
X509KeyStorageFlags.MachineKeySet|
X509KeyStorageFlags.PersistKeySet|
X509KeyStorageFlags.Exportable
Setting Load User Profile to true in Applicaion Pool
Below is a snippet of how I read the certificate from the store
private byte[] ReadCertificate(string certificateThumbprint)
{
X509Store store = new X509Store("MY",StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection =
(X509Certificate2Collection)store.Certificates;
X509Certificate2Collection signingCert =
collection.Find(X509FindType.FindByThumbprint,
certificateThumbprint,false);
byte[] rawdata = signingCert[0].RawData;
store.Close();
return rawdata;
}
Does anybody have any idea on how I can fix this?
byte[] rawdata = signingCert[0].RawData;
actually, this returns only public part of the certificate, without private key reference. Instead, you shall consider to return entire X509Certificate2 object.

Find certificate location on box/server

I am trying to find where the certificate is stored on my local machine and then as well as our dev servers. I can go Run -> MMC -> File - > Add/Remove SnapIns and select certificates and Current User and see my personal certificates. However, I am trying to utilize this code for an HttpWebRequest and I cannot find the url.
string certPath = #"e:\mycertificate.cer"; //This Value
X509Certificate myCert = X509Certificate.CreateFromCertFile(certPath);
request.ClientCertificates.Add(myCert);
In another area we set up a proxy and do it like this.
proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, CertificateName);
So obviously a little different implementation and I am unsure as to where/how to find the location to fill in for the first example.
Solution that worked for me
public WebRequest GetWebRequest(string address)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
X509Certificate myCert = null;
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates)
{
if (mCert.FriendlyName.Contains("certname"))
{
myCert = mCert;
}
}
if (myCert != null) { request.ClientCertificates.Add(myCert); }
return request;
}
Assuming like you want to pick a certificate somehow and not really care if it is from file or not. In this case you can use certificate store object and find one you need (i.e. by thumbprint). Check out this Get list of certificates from the certificate store in C# and MSDN article on X509Store.Certificates which contains sample too:
X509Store store = new X509Store("My");
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 mCert in store.Certificates){
//TODO's
}

Signing X509 Certs w/BouncyCastle - invalid digital signature [duplicate]

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
Generated signed X.509 client certificate is invalid (no certificate chain to its CA)
I followed the example at:
http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation
But the resulting signed client certificate has the following error when opened in windows:
"This file is invalid for use as the following: Security Certificate"
If I install it anyway and view it with certmgr, the certification path looks OK - I see my self-signed Certificate Authority (which is fine, no problems there) but the client cert has the following status:
"This certificate has an invalid digital signature."
If I call X509Certificate.Verify() it throws the following exception:
"Public key presented not for certificate signature"
Yet I'm using the same exact public key extracted from the Pkcs10CertificationRequest and when I called Verify() on that it's fine.
Any ideas? After days of struggling through this, I've got all the pieces working except this last one - and what's really confusing is that my self-signed CA cert is fine. There's just something going on with the client cert. Here's the entire block of code:
TextReader textReader = new StreamReader("certificaterequest.pkcs10");
PemReader pemReader = new PemReader(textReader);
Pkcs10CertificationRequest certificationRequest = (Pkcs10CertificationRequest)pemReader.ReadObject();
CertificationRequestInfo certificationRequestInfo = certificationRequest.GetCertificationRequestInfo();
SubjectPublicKeyInfo publicKeyInfo = certificationRequestInfo.SubjectPublicKeyInfo;
RsaPublicKeyStructure publicKeyStructure = RsaPublicKeyStructure.GetInstance(publicKeyInfo.GetPublicKey());
RsaKeyParameters publicKey = new RsaKeyParameters(false, publicKeyStructure.Modulus, publicKeyStructure.PublicExponent);
bool certIsOK = certificationRequest.Verify(publicKey);
// public key is OK here...
// get the server certificate
Org.BouncyCastle.X509.X509Certificate serverCertificate = DotNetUtilities.FromX509Certificate(System.Security.Cryptography.X509Certificates.X509Certificate.CreateFromCertFile("servermastercertificate.cer"));
// get the server private key
byte[] privateKeyBytes = File.ReadAllBytes("serverprivate.key");
AsymmetricKeyParameter serverPrivateKey = PrivateKeyFactory.CreateKey(privateKeyBytes);
// generate the client certificate
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
generator.SetIssuerDN(serverCertificate.SubjectDN);
generator.SetNotBefore(DateTime.Now);
generator.SetNotAfter(DateTime.Now.AddYears(5));
generator.SetSubjectDN(certificationRequestInfo.Subject);
generator.SetPublicKey(publicKey);
generator.SetSignatureAlgorithm("SHA512withRSA");
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(serverCertificate));
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(publicKey));
var newClientCert = generator.Generate(serverPrivateKey);
newClientCert.Verify(publicKey); // <-- this blows up
return DotNetUtilities.ToX509Certificate(newClientCert).Export(X509ContentType.Pkcs12, "user password");
I figured this out. If you call X509Certificate.Verify(publicKey) you have to pass the CA's public key, not the client's public key from the Pkcs10CertificationRequest.

Categories

Resources