Load a certificate to sign a SOAP envelope in C# with WCF - c#

I try to load a x509 certificate to use in a WCF client. for this i use the SetDefaultCertificate function but this function throw a exception.
var clientWS = new WS_eFacturaSoapPortClient();
clientWS.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
StoreLocation.CurrentUser, StoreName.My,
X509FindType.FindBySubjectKeyIdentifier, "79852b4fab95e8cd1f6e36167bbb895bd4cbe767");
The exception:
Cannot find the X.509 certificate using the following search criteria:
StoreName 'My', StoreLocation 'CurrentUser', FindType
'FindBySubjectKeyIdentifier', FindValue
'79852b4fab95e8cd1f6e36167bbb895bd4cbe767'.
But if I do this...
X509Certificate2 cert = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = store.Certificates.Find(
X509FindType.FindBySubjectKeyIdentifier, "79852b4fab95e8cd1f6e36167bbb895bd4cbe767", true);
cert = col[0];
}
// Cerrar el store
finally { store.Close(); }
The certificate is founded.
What i do wrong?, is posible add the x509Certificate2 to the ClientCredentials?

I changed the FindType to FindBySerialNumber and it works.
clientWS.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
StoreLocation.CurrentUser, StoreName.My,
X509FindType.FindBySerialNumber, "0cf43655217b8853e2df0b931d2c352afa93d9");
this post helped me.

Related

Adding a certificate to Store then retrieve it

Disclaimer: I am 2 days into reading about Certificates/RSA Algorithms and Encrypt/Decrypt.
I am trying to do a small app that communicates with Windows Key Store ( Certificate Store ) and where I should be able to read certificates/add certificates.
I have created a method to add a certificate.
public void AddKey()
{
CngKey cngKey;
CngKeyCreationParameters cng = new CngKeyCreationParameters
{
KeyUsage = CngKeyUsages.AllUsages
};
if (!CngKey.Exists(KEY_NAME))
{
cngKey = CngKey.Create(CngAlgorithm.Rsa, KEY_NAME, cng);
}
else
{
cngKey = CngKey.Open(KEY_NAME);
}
RSACng rsaKey = new RSACng(cngKey)
{
KeySize = 2048
};
byte[] rsaPrvKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPrivateBlob);
byte[] rsaPubKeyExport = rsaKey.Key.Export(CngKeyBlobFormat.GenericPublicBlob);
CngKey cngPrv = CngKey.Import(rsaPrvKeyExport, CngKeyBlobFormat.GenericPrivateBlob);
CngKey cngPub = CngKey.Import(rsaPubKeyExport, CngKeyBlobFormat.GenericPublicBlob);
//var signed = Sign512(Constants.STRING_TO_ENCODE.ToByteArray(), rsaPrvKeyExport);
string exportPrivateKey = Convert.ToBase64String(rsaKey.ExportPkcs8PrivateKey());
string pemString = $"{Constants.RSA_KEY_HEADER}\n{exportPrivateKey}\n{Constants.RSA_KEY_FOOTER}";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
var certificate = BuildSelfSignedServerCertificate(rsaKey);
var thumbprint = certificate.Thumbprint;
store.Certificates.Add(certificate);
store.Close();
}
I know not all lines in this code are needed, but I am in the learning process.
So what I do here is create a cngKey
Open the store.
Create a Certificate from my cngKey
Add the certificate to the store
Close the store.
I generate a certificate from a cngKey using this code
private X509Certificate2 BuildSelfSignedServerCertificate(RSA key)
{
X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={Constants.CERTIFICATE_NAME}");
var request = new CertificateRequest(distinguishedName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
//request.CertificateExtensions.Add(sanBuilder.Build());
var certificate = request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
certificate.FriendlyName = Constants.CERTIFICATE_NAME;
return new X509Certificate2(certificate.Export(X509ContentType.Pfx, "WeNeedASaf3rPassword"), "WeNeedASaf3rPassword", X509KeyStorageFlags.MachineKeySet);
}
This gives no error, but when I open the certificate store I can't find it
I also tried to retrive it programmaticaly
using this bit of code
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates;
but my certificate is not here.
What am I missing, I am sure I am doing something wrong, but being new to this, I have no idea what.
Online I could not find a complete example of what I tried to achieve here.
As #dimitar.bogdanov pointed out in comments, you are not adding the certificate to the store:
store.Certificates.Add(certificate);
here you are adding the certificate only to disconnected collection. Any changes in this collection object will not reflect actual store state. Instead, you have to use X509Store.Add method to update actual store.

Best practice siging with rsa certificate

I have a web service which need the body signing on all the requests. so i wonder what is the best practice in order to do that? I'm using this method in order to sign the string but i guess its not the best way. because my private key never disposes:
using (var provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(ProjectValues.PrivateKey.ExportParameters(true));
encryptByteList = provider.SignData(content, CryptoConfig.MapNameToOID("SHA256"));
}
I already read the certificate and private key once when the project loaded for first time. Like this:
Certificate = new X509Certificate2("my pfx path", "***", X509KeyStorageFlags.Exportable);
PrivateKey = (RSACryptoServiceProvider)Certificate.PrivateKey;
Then I found this method:
var cert = new X509Certificate2("my pfx path", "***", X509KeyStorageFlags.Exportable);
using (var rsa = cert.GetRSAPrivateKey())
{
encryptByteList = rsa.SignData(content, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
but this one works if i read certificate from file on every requests. obviously its not a efficient way

CanĀ“t find installed but expired certificate?

One of my certificates have been expired. This makes the following code fail :
X509Store store = new X509Store(settings.CertificateStore, settings.CertificateLocation);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByThumbprint, settings.Thumbprint, true);
store.Close();
if (certs.Count > 0)
{
LogHandler.Instance.Log(LogLev.Info, $"UseSecureConnection > Location:{settings.CertificateLocation}, Store:{settings.CertificateStore}, Thumbprint:{settings.Thumbprint}");
_serviceHost.Credentials.ServiceCertificate.SetCertificate(settings.CertificateLocation,
settings.CertificateStore, X509FindType.FindByThumbprint, settings.Thumbprint);
}
else
throw new Exception("Could not find certificate with thumbprint " + settings.Thumbprint);
This codes worked great uptil certificate got expired.
How do i fetch it even if its expired?
store.Certificates.Find(X509FindType.FindByThumbprint, settings.Thumbprint, false)
should do it. The last parameter is validOnly.

Exception on attempt to create not self-signed certificate

I want to switch from self-signed certificate per device to pair of certificates, one of which is previously generated, placed in Trusted Root Certificate Authorities store, is same for all devices, and works as root CA for second certificate, which is generated per device, and placed in Personal store.
I would like to not use makecert, since creating signed certificate shows up UI, which I want to avoid. Also, OpenSSL can't be used due to some license-related stuff (although I have working solution with it). So, for now I'm working with small C# tool, based on CertEnroll lib.
This is how I create pfx for first, root CA certificate.
makecert -n "CN=Root CA" -cy authority -r -a sha256 -len 2048 -sv root.pvk root.cer
pvk2pfx -pvk root.pvk -spc root.cer -pfx root.pfx -pi 123 -po 123
To create certificate from C# code, I've referenced questions How to create self-signed certificate programmatically for WCF service? and C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll.
So far, I have following code. Method for certificate generation:
/// <summary>
/// Generates self-signed certificate with specified subject, which will expire after specified timespan.
/// </summary>
public X509Certificate2 CreateCertificate(string subjectName, TimeSpan expirationLength, X509Certificate2 issuer = null)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName);
var issuerName = new CX500DistinguishedName();
if(issuer != null)
{
issuerName.Encode(issuer.Subject);
}
var privateKey = new CX509PrivateKey
{
ProviderName = "Microsoft Strong Cryptographic Provider",
Length = 2048,
KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE,
KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG |
X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG,
MachineContext = true,
ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG
};
privateKey.Create();
// Use the stronger SHA512 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
if (issuer != null)
cert.Issuer = issuerName;
else
cert.Issuer = dn;
cert.NotBefore = DateTime.Now.Date;
cert.NotAfter = cert.NotBefore + expirationLength;
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
if(issuer != null)
{
var signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(true, X509PrivateKeyVerify.VerifyAllowUI, EncodingType.XCN_CRYPT_STRING_HEX, issuer.GetRawCertDataString());
cert.SignerCertificate = signerCertificate;
}
cert.Encode();
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
var csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new X509Certificate2(Convert.FromBase64String(base64encoded), "",
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
}
Simple application to find existing certificate and create new one based on it:
static void Main(string[] args)
{
var certificateGenerator = new CertificateGenerator();
X509Certificate2 rootCA;
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
rootCA = store.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(c => c.Subject.StartsWith("CN=Root CA", StringComparison.Ordinal));
store.Close();
}
if (rootCA == null)
throw new Exception("Can't find root CA certificate");
var testCert = certificateGenerator.CreateCertificate("Test", new TimeSpan(3650, 0, 0, 0), rootCA);
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(testCert);
store.Close();
}
}
The thing is, that it works great, if I try to reference certificate not in Trusted Root Certificate Authorities, but in Personal (even if I have password on certificate). But if I try to create certificate based on CA certificate from Trusted Root Certificate Authorities, I receive exception on signerCertificate.Initialize, saying
Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)
So, what am I missing?
ISignerCertificate::Initialize requires that the private key be bound via the Requests or My store:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa376832(v=vs.85).aspx:
If a private key is needed, only the personal and request stores are
searched.
If a private key is not needed, the root and intermediate CA
stores are also searched.
Windows expects that you only put the public portion of the CA into the CA (intermediate) or Root/ThirdPartyRoot stores, and that if you're the issuer you'll ALSO have it installed (with the private key now) into CurrentUser\My or LocalMachine\My.

Azure Not Returning uploded certificates

I have uploaded Certificates thorough azure new portal while i am not getting these certificates back here is my code
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates;
try
{
}
finally
{
store.Close();
}
return certificates;
These are the certificates which I always get
enter image description here
Also i have follow this article https://azure.microsoft.com/en-us/blog/using-certificates-in-azure-websites-applications/
Anybody know the reason why i am not getting all the certificates and why i am getting these certificates ?? please help
instead of geeting all certificates i just received these 4, and in the local enviorment i get all certificates which are install on my machine
From my test, the article in your reply help us to use Certificates in Azure web app. However we only could query the certificates with the following conditions:
1) the certificate has been uploaded to Azure web app
2) setting WEBSITE_LOAD_CERTIFICATES in Azure portal with its value set to the certificate thumbprint
It is different with your test on your local machine because Azure web app run in sandbox. For more information about Azure web app sandbox, please refer to this article.
Well, I'm using this function I had found somewhere and is working fine. If you have uploaded all the certificates properly, can you please try running this piece of code. I know it looks the same but you can't really tell.
private X509Certificate2 GetStoreCertificate(string thumbprint)
{
List<StoreLocation> locations = new List<StoreLocation> { StoreLocation.CurrentUser, StoreLocation.LocalMachine };
foreach (var location in locations)
{
Console.WriteLine("location: " + location.ToString());
X509Store store = new X509Store("My", location);
try
{
Console.WriteLine("Try, store.Open...");
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
Console.WriteLine("store.Opened..." + store.Certificates.Count.ToString());
foreach (X509Certificate2 cert in store.Certificates)
{
Console.WriteLine("X509Certificate2 Thumbprint : " + cert.Thumbprint);
}
foreach (X509Certificate cert in store.Certificates)
{
Console.WriteLine("X509Certificate Thumbprint : " + cert.Issuer);
}
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
Console.WriteLine("Finding certificate.." + certificates.Count.ToString());
if (certificates.Count == 1)
{
Console.WriteLine("Atleast one found!!!");
return certificates[0];
}
}
finally
{
store.Close();
}
}
throw new ArgumentException(string.Format("A Certificate with Thumbprint '{0}' could not be located.", thumbprint));
}

Categories