'Cannot find the requested object' exception while creating X509Certificate2 from string - c#

I am trying to create X509Certificate2 from string. Let me show an example:
string keyBase64String = Convert.ToBase64String(file.PKCS7);
var cert = new X509Certificate2(Convert.FromBase64String(keyBase64String));
and keyBase64String has a such content: "MIIF0QYJKoZI ........hvcNAQcCoIIFwjCCBb4CA0="
and file.PKCS7 is byte array which I downloaded from database.
I've got the following exception when creating X509Certificate2:
Cannot find the requested object
And the stack trace:
"Cannot find requested object" X509Certificate2 Exception "Cannot find
requested object"} at
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32
hr) at
System.Security.Cryptography.X509Certificates.X509Utils._QueryCertBlobType(Byte[]
rawData) at
System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[]
rawData, Object password, X509KeyStorageFlags keyStorageFlags) at
System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[]
rawData) at
WebApp.SoupController.d__7.MoveNext()
in
D:\Projects\WebApp\Controllers\SoupController.cs:line
118
Please, say me what I am doing wrong. Any help would be greatly appreciated!

If file.PKCS7 represents a PKCS#7 SignedData blob (what gets produced from X509Certificate2.Export(X509ContentType.Pkcs7) or X509Certificate2Collection.Export(X509ContentType.Pkcs7)) then there are two different ways of opening it:
new X509Certificate2(byte[])/new X509Certificate2(string)
The single certificate constructor will extract the signing certificate of the SignedData blob. If this was just being exported as a collection of certs, but not signing anything, there is no such certificate, and so it fails with Cannot find the original signer. (Win 2012r2, other versions could map it to a different string)
X509Certificate2Collection::Import(byte[])/X509Certificate2Collection::Import(string)
The collection import will consume all of the "extra" certificates, ignoring the signing certificate.
So if it's really PKCS#7 you likely want the collection Import (instance) method. If it isn't, you have some odd variable/field/property names.

The constructor of of X509Certificate2 expects to get a the certificate file name, but you are giving it a key (X509Certificate2 Constructor (String))
I assume that keyBase64String is the certificate key, and that the certificate is installed on the machine that executes the code. Try this:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certCollection = store.Certificates.Find(X509FindType.FindByThumbprint, keyBase64String , false);
var cert = certCollection[0];
You can also try FindByKeyUsage, FindBySubjectKeyIdentifier, or other types of X509FindType Enumeration

Related

Getting Exception "Invalid Provider type specified" or "Key does not exist" while getting Private key from X509Certificate2 Occasionally

I am getting one of the following Exceptions while trying to get a private key from X509Certificate2 certificate:
System.Security.Cryptography.CryptographicException: Invalid provider type specified.
OR
System.Security.Cryptography.CryptographicException: Key does not exist at the following line of code: RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)digiSignCert.PrivateKey;
Stacktrace:
System.Security.Cryptography.CryptographicException: Key does not exist. at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle) at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair() at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize) at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey() at Api.CertificateUtil.GetSignedXml(String xml, X509Certificate2 privateCert)
Code:
public static RSACryptoServiceProvider rsaKey = null;
public X509Certificate2 _PrivateCert;
public APISearch()
{
byte[] privateCert = null;//We get the actual certificate file data here
GetPrivateCerificate(privateCert, "abc#123");
GetSignedXml(_PrivateCert);
}
public void GetPrivateCerificate(byte[] privateCert, string pwd)
{
_PrivateCert = new X509Certificate2(privateCert, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
}
public void GetSignedXml(X509Certificate2 privateCert)
{
rsaKey = (RSACryptoServiceProvider)privateCert.PrivateKey; //Occassional Exception
}
Expected result: (RSACryptoServiceProvider)privateCert.PrivateKey should always produce a private key.
Actual result: Sometimes the aforementioned exceptions are thrown at this line:
rsaKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
and sometimes the private key is successfully being fetched from the certificate file. As of now, we have been unable to track the pattern of this problem.
RSACryptoServiceProvider is a type that performs RSA via the Window Cryptographic API (CAPI) library. When .NET was first created CAPI was new and always the right answer (on Windows). Starting in Windows Vista there was a new library: Cryptography: Next Generation (CNG). CNG, for compatibility, understands how to work with CAPI. But CAPI can't "be CAPI" and "understand CNG". The exceptions you are seeing are when a PFX indicated that the private keys should be stored via CNG (or an in-store cert indicates that it's private keys are stored via CNG).
When .NET Framework was adding RSACng it was decided that too many people had already written the line (RSACryptoServiceProvider)cert.PrivateKey, so that property can't ever return an RSACng instance. Instead, in .NET 4.6 new (extension) methods were made: cert.GetRSAPublicKey() and cert.GetRSAPrivateKey(), they return RSA instead of AsymmetricAlgorithm. Also in .NET 4.6 the RSA base class was enhanced to have the Sign/Verify and Encrypt/Decrypt operations moved down (though with different signatures, since RSA has gained new options since CAPI was written).
Expected result: (RSACryptoServiceProvider)privateCert.PrivateKey should always produce a private key.
The actual truth is cert.PrivateKey (and cert.PublicKey.Key) is/are soft-deprecated. You shouldn't call it/them anymore. RSA (4.6), ECDSA (4.6.1) and DSA (4.6.2) all have Get[Algorithm]{Public|Private}Key methods.
(RSACryptoServiceProvider)cert.PrivateKey => cert.GetRSAPrivateKey()
rsaCSP.Encrypt(data, false) => rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1)
rsaCSP.Encrypt(data, true) => rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA1)
rsaCSP.SignData(data, "SHA256") => rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
Similar for Decrypt, SignHash, VerifyData, VerifyHash; and similar for ECDsa and DSA.
Finally, please don't hard-cast the return value of those methods, it changes as it needs to... on Windows it can return either RSACng or RSACryptoServiceProvider, on Linux (.NET Core) it currently returns RSAOpenSsl, and on macOS (.NET Core) it returns an uncastable object.

Azure, App-service, create X509Certificate2 object from string

Having an App-service in Azure, and working on the AzureServiceManagementAPI, I was downloading the file that contains the managememnt certificate for each subscription.
Any how using the certificate string from the file I'm trying to create a X509Certificate2 object.
string cerStr = subscription.Attribute("ManagementCertificate").Value;
X509Certificate2 x509 = new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
The constructor of X509Certificate2 throw an exception
Access denied.
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32
hr) at
System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[]
rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet,
SafeCertContextHandle& pCertCtx) at
System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[]
rawData, Object password, X509KeyStorageFlags keyStorageFlags)
Since no one has answered this questions, I will try and have go at it. Please correct me if I am wrong, but the problem I think is the following line of code:
new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
This line of code will try to add a new certificate to the certificate store of the virtual machine. All certificates used by the runtime, needs to be hosted in a store somewhere. This is not a good idea because the certificate store of the virtual machine hosting the app service is nothing that you should be storing anything in, it's part of the infrastructure which is not of your concern when you are working with app services.
What you need to do is to upload the certificate through the azure portal instead (if they are not already there). I ended up reusing a SSL certificate already in place for this purpose. When this is done, you can retreive that certificate in code. You will need to add a new App Setting under "Application Settings" key in the Azure portal for your app service, named WEBSITE_LOAD_CERTIFICATES. The value should be the thumbprint of the certificate.
To retrieve the cert, you should do something like this:
public async Task<X509Certificate2> GetCertificate(string certificateThumbprint)
{
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.OfType<X509Certificate2>()
.FirstOrDefault(x => x.Thumbprint == certificateThumbprint);
store.Close();
return cert;
}
You might be able to get thumbprint of the cert by navigating your subscription using the azure resource explorer https://resources.azure.com/
As Fredrik mentioned the issue is due to the code
X509Certificate2 x509 = new X509Certificate2(Convert.FromBase64String(cerStr), string.Empty, X509KeyStorageFlags.MachineKeySet)
In the Azure WebApp, if we try to use the certificate, we need to upload the certificate from the Azure portal. Add the WEBSITE_LOAD_CERTIFICATES with thumbprint value in the Azure WebApp application. More detail info please refer to blog.
Web application to access the certificate, snippet code from the blog
static void Main(string[] args)
{
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
// Replace below with your cert's thumbprint
“E661583E8FABEF4C0BEF694CBC41C28FB81CD870”,
false);
// Get the first cert with the thumbprint
if (certCollection.Count > 0)
{
X509Certificate2 cert = certCollection[0];
// Use certificate
Console.WriteLine(cert.FriendlyName);
}
certStore.Close();
}

how to load password protected certificates from the X509Store?

I am building an ACS protected Azure WCF service that will require clients to authenticate via a certificate.
I would like the client (and the server) to load their respective password certs from the X509Store instead of from the file system.
I am using this code:
private static X509Certificate2 GetCertificate(string thumbprint)
{
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certStore.Certificates.Find(
X509FindType.FindByThumbprint,
thumbprint, false);
certStore.Close();
if (certCollection.Count == 0)
{
throw new System.Security.SecurityException(string.Format(CultureInfo.InvariantCulture, "No certificate was found for thumbprint {0}", thumbprint));
}
return certCollection[0];
}
Problem is, it's not loading the private key which it needs for authentication. I have tried to modify the return statement to this:
return new X509Certificate2(certCollection[0].Export(X509ContentType.Pfx, "password"));
However, this fails with a CryptographicException "The spcecified network password is incorrect".
Edit:
The .Export() method works properly if you don't pass the password argument in.
Any help on this?
When you export, the password you provide is the password you want to use for the exported file, it's not the password for the source certificate.
I'm not sure what you can do with X509Store and password-protected certs because the password should be supplied to the X509Certificate constructor and you get already-instantiated objects out of the store.
I think you can just get the raw data from the cert you want and construct a new one with the password you want. For example:
X509Certificate2 cert = new X509Certificate2(certCollection[0].GetRawCertData, password);
I would also suggest you try to use SecureString when dealing with passwords (but that's a different bag of worms...)
I used the Export without the 'password' parameter and it worked without issue.
When the cert was imported into the certificate store, I think the key has to be marked as "exportable" otherwise I don't think you can export the private key..

Parse PKCS #7 SSL Certificate Chain (.p7b) without private key?

I have a PKCS #7, signed, .p7b file which contains an X509 SSL certificate and the intermediate and root CA certs it was signed with. I need to use C# to parse the .p7b file, pull out the SSL certificate, and pull some values off of it (expiry date, DN, etc).
I've tried reading it as an X509 certificate like so:
//certContent is a byte array with the p7b file contents
X509Certificate2 cert = new X509Certificate2(certContent);
That works fine with a regular .cer certificate, but throws a CryptographicException when used with a .p7b certificate. This is because the .p7b contains the entire certificate chain.
I've also tried parsing it as a SignedCms object, then iterating through the certificate chain and pulling out my SSL certificate:
SignedCms certContainer = new SignedCms();
certContainer.Decode(certContent);
foreach(X509Certificate2 cert in certConatiner.Certificates)
{
...
}
However that throws an exception on Decode saying ASN1 bad tag value met. After some searching, I believe that is because I do not have the private key which was used to create the certificate and/or sign the certificate.
Does anyone know how I can parse this .p7b certificate chain using C#?
Well, I'm an idiot. I opened up the .p7b file and realized it was just base64 on the inside. I pulled out the base64, decoded that, then parsed that as a signed CMS and all is well.
String content = Encoding.UTF8.GetString(certContent);
String base64Content = content.Replace("-----BEGIN CERTIFICATE-----", "").Replace("-----END CERTIFICATE-----", "").Replace("\r", "").Replace("\n", "");
byte[] decodedContent = Convert.FromBase64String(base64Content);
SignedCms certContainer = new SignedCms();
certContainer.Decode(decodedContent);

Loading the Jira Public Certificate in .Net from a string (how to convert ASN.1 encoded SubjectPublicKeyInfo to X509 Cert in .Net)

I am building an oauth 1.0a service that will be consumed by a gadget within Jira, it's a .Net 3.5 Application written in C#.
Jira makes requests to this service using the RSA-SHA1 signature method, which means to verify the signature of the request I need create an X509Certificate instance form their public cert.
Within the Jira application you can get the public cert by going to the consumer info screen (which also has the consumer key for Jira etc.) and it presents the public key in this format:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCObJRTGSZbAo
jRkvKmm0cwFXnKcPMfR4t/sghvLe/+QVs6TJOz5cUh5UokSqyz
VeMsL0jomP18ZcR3SPcIFT7xtOGQjLwLk7ghfYSsxjTGs9VxsC
/PQk5OQRP3v43IxFNF3M2SYhFWJZTOnqrab5AsMh2Kxdv+D69D
CINXCu5ltQIDAQAB
Looking at the Jira code which generates this key I can see it's (supposedly) PEM encoded without the BEGIN/END certificate header/footer.
RSAKeys.toPemEncoding(consumer.getPublicKey())
RSAKeys is an open source class found here:
https://studio.atlassian.com/source/browse/OAUTH/trunk/api/src/main/java/com/atlassian/oauth/util/RSAKeys.java?r=HEAD
I wish to load this public cert (key) into an X509Certificate instance within .Net, but my attempts so far have failed. Here's the code I have:
static readonly Regex stripRegex = new Regex("-----[A-Z ]*-----");
public string ConvertFromOpenSsl(string key)
{
return stripRegex.Replace(key, "").Replace("\r", "").Replace("\n", "");
}
public X509Certificate2 GetConsumerCertificate(IConsumer consumer)
{
string cert =
#"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCObJRTGSZbAo
jRkvKmm0cwFXnKcPMfR4t/sghvLe/+QVs6TJOz5cUh5UokSqyz
VeMsL0jomP18ZcR3SPcIFT7xtOGQjLwLk7ghfYSsxjTGs9VxsC
/PQk5OQRP3v43IxFNF3M2SYhFWJZTOnqrab5AsMh2Kxdv+D69D
CINXCu5ltQIDAQAB";
string converted = ConvertFromOpenSsl(cert);
var bytes = Convert.FromBase64String(converted);
var cert = new X509Certificate2(bytes); // throws here
But on the last line of code I have an exception thrown:
System.Security.Cryptography.CryptographicException: Cannot find the requested object.
at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
at System.Security.Cryptography.X509Certificates.X509Utils._QueryCertBlobType(Byte[] rawData)
at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(Byte[] data)
at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData)
I'm pretty sure I am missing something elementary, but I can think what it is.
UPDATE
OK, on further investigation it appears that this is a SubjectPublicKeyInfo serialization of the public key, so it's ASN.1, base 64 encoded (162 bytes unencoded), which is the default output from Java using java.security.PublicKey.getEncoded().
So given all that - is there any easy way to create an X509Certificate2 instance wrapping this public key - or is additional metadata required beyond the public key to create an x509Certificate2 instance?
Jira should provide you with a Certificate (not just a public key).
Typically the Java world will give a base64 encoded or PEM certificate. X509Certificate2 from .Net can automatically .Load a base64, PEM or binary certificate.
you can generate your XML RSA certificate via .NET using RSACryptoServiceProvider. This will give you XML (FromXmlString method), the public key then needs to be encoded, for example by using this service:
https://superdry.apphb.com/tools/online-rsa-key-converter
and then used to create application link to JIRA.
The private key in XML form you got previously, can be used for signing .NET app requests directly.
I personally used DonNetAuth library for signing, exchannging tokens, etc and it works for me. The only bug I encountered was regarding jql queries, where the signing needed a bit of tweaking to work correctly. Here is the link:
http://samondotnet.blogspot.sk/2012/12/introduction-to-dotnetauth.html
Additionally see this link:
https://answers.atlassian.com/questions/172760/is-there-any-jira-oauth-implementation-example-in-net
Hope this helps.

Categories