I found this article in Rahul's Blog about getting the certificate associated with the inserted smart card.
Rahul suggests this approach:
var smartCardCerts = new List<X509Certificate2>();
var myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
foreach(X509Certificate2 cert in myStore)
{
if( !cert.HasPrivateKey ) continue; // not smartcard for sure
var rsa = cert.PrivateKey as RSACryptoServiceProvider;
if( rsa==null ) continue; // not smart card cert again
if( rsa.CspKeyContainerInfo.HardwareDevice ) // sure - smartcard
{
// inspect rsa.CspKeyContainerInfo.KeyContainerName Property
// or rsa.CspKeyContainerInfo.ProviderName (your smartcard provider, such as
// "Schlumberger Cryptographic Service Provider" for Schlumberger Cryptoflex 4K
// card, etc
var name = cert.Name;
rsa.SignData(); // to confirm presence of private key - to finally authenticate
}
}
However, if the smart card is not inserted, a Windows Security dialog pops up asking the user to select a smart card device.
Is there a way to prevent this popup and instead throw an exception immediately?
Most important to me is this piece of information:
rsa.CspKeyContainerInfo.HardwareDevice
Does somebody know any other way to access this info without popup dialogs if smart card is missing?
Edit:
The Windows Security dialog pops up at this line of code:
var rsa = cert.PrivateKey as RSACryptoServiceProvider;
There is no way to prevent the popup using this code. There are other approaches, for example using PCSC or Setting the PIN of a smartcard programmatically
Related
So, I having trouble to get the private key from the certificate. This is my first time working with certificates and I have tried to find a solution to this but I cant make i work. It's a console application and a need the key to sign a SOAP message/request.
Here is a code sample; (Let me know if you need anything more)
public static X509Certificate2 FindCertificate(string issuedBy)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
//We gets the certificate from store
var certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.Issuer.Contains($"CN={issuedBy}"));
if (certificate.HasPrivateKey) // Result: True
{
//PrivateKey throws error: Keyset dosent exist
var key = certificate.PrivateKey;
}
if (certificate == null)
{
throw new ArgumentException($"Could not find certificate issued by: '{issuedBy}'", nameof(issuedBy));
}
var expireDate = DateTime.Parse(certificate.GetExpirationDateString());
if (expireDate < DateTime.Now)
{
throw new Exception($"Certificate has already expired: '{expireDate}'");
}
return certificate;
}
An alternative to your own solution (so you don't have to run VS in administrator mode is to give your own user privileges to read the private key.
Open the certificate manager for your machine certificates (type in "certificates" in Windows home menu search and choose "Manage computer certificates"
Find the certificate and right click -> "All Tasks" -> "Manage Private Keys..."
Click "Add..."
Type in your username (and click "Check Names" to see if you typed it in correctly)
Click "OK" and "OK"
Now you should be able to run your application (and VS) normally with access to the private key.
I was able to get it to work now just by start Visual Studio in Admin mode.
I am trying to use RSACryptoServiceProvider with CspParameters that point to a global pin.
It works correctly if I use an application pin but when I use the global pin it fails with:
"The card cannot be accessed because the wrong PIN was presented."
Will it work when I use a global pin? Is there an option that tells it what type of pin to look for?
Thanks in advance.
Update:
I am retrieving the discovery object from the smart card if it exists.
This will tell me two things I want to know.
1). If the card has both application and global pins. (first byte of pin usage >= 60)
2). Which pin is considered primary. (second byte 0x10 = app, 0x20 = global)
I have a card, the NIST Test Pivcard 3, which has both pins but the global pin is primary. For this card when I enter the global pin on my test form, I can do a verify against it and it validates the pin correctly. (CLA=0x00, INS=0x20, P1=0x00, P2=0x00, Lc=0x8)
I can do the same for this card if I enter the application pin instead (with P2 set to 0x80) and it verifies it correctly.
After I verify the pin, set the AID and get some other x509 data from the card, I attempt to sign some hashed data using the card's private key.
Using RSACryptoServiceProvider and CspParameters it fails whenever I pass it the global pin. I get "The card cannot be accessed because the wrong PIN was presented."
If I pass it a valid application pin, then it works fine.
My code looks like this:
try
{
SecureString ss = new SecureString();
char[] PINs = PIN.ToCharArray();
foreach (char a in PINs)
{
ss.AppendChar(a);
}
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
csp.KeyPassword = ss;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
byte[] data = File.ReadAllBytes(hashFile);
sig = rsa.SignHash(data, "SHA1");
bool verified = rsa.VerifyHash(data, CryptoConfig.MapNameToOID("SHA1"), sig);
}
catch (Exception ex)
{
txt_msg.Text = ex.Message;
etc...
}
Is there some flag I am missing here to say that the pin being used is a global pin? Or are we not allowed to use a global pin? Or am I missing some other thing here? This is my first attempt to use RSACryptoServiceProvider and I'm probably missing some fundamentals.
Any suggestions would be appreciated.
You seem to assume, that PINs are somewhat exchangeable in the sense, that a global PIN is an appropriate substitute for an application-specific one. This is not true.
While a card could have been set up this way (accepting global PIN say #1 OR application specific PIN #2), your card is obviously not. If the card does not offer the choice, the service provider can't succeed. Even if the service provider uses the PIN you want and the comparison succeeds, the card will not allow usage of the private key.
And no, there is no way that you change the ID of the PIN required for signature according to your liking, since this path would then also be available for an attacker.
(All of this answers assume, that you are not allowed to modify the card content, e. g. you are a normal card holder.)
I am using a Gemalto Smart card to sign XML documents.
I have a method which returns the certificate and searches by the thumbprint that is hard coded.
I am unable to obtain the private key from that Smart card and sign the doc with it.
It returns null when I debug the app.
My goal is to get the private key and then ask the user for the PIN to authorize the signing of the document.
public static X509Certificate2 GetDefaultCertificateStoredOnTheCard()
{
X509Store store = new X509Store("MY", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
// by thumbprint, there is only one
certs = certs.Find(X509FindType.FindByThumbprint, "6BB4F9D483206F44A992799541114536579CF2B3", true);
if (certs.Count == 0)
{
throw new ArgumentException("Please insert smart card to obtain certificate.");
}
X509Certificate2 cert = certs[0];
RSACryptoServiceProvider key;
if (cert.HasPrivateKey)
{
// software cert
key = cert.PrivateKey as RSACryptoServiceProvider;
}
else
{
// certificate from smartcard
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
key = new RSACryptoServiceProvider(csp);
}
return cert;
}
As you can see if the key is null, set the key to use the Microsoft Base Smart Card Crypto Provider.
I have noticed on device manager that my Smart Card Reader is Microsoft Usbccid Smartcard Reader.
Not sure if I should set something else here , it brings this window and an error with it.
The very purpose of a smart card is to protect confidential data. There are no more confidential data than the private key (often this never leaves the smart card), so it is a sign of correct functioning, that you don't get it.
Actually you have to let the smart card do the signature, probably by sending the hash value of your XML document.
Just to answer my question. The whole problem was the .dll. I installed wrongly another provider of electronic signature and it is using that .dll instead of the one that I used for the signing.
When I understood that, it was easy fix, just uninstall the app for the provider which uses the wrong .dll.
I have a .p7b file with a few certificates and i want to install them in the "Enterprise Trust" Store. A program i want to use expects it there.
I have writte code in c# which extract all the certificates from the file and installs them into a X509Store ("Storename.My") which works.
If i try to use the same code to write in a different store it (which already exists) it creates a new empty store and writes in there.
The StoreName.My is taken from system.Security.Cryptography.X509Certificates public enum StoreName, but there is no option for the "Enterprise Trust" store.
So i tried to use the constructor where i can give the StoreName as a string.
I use the certmgr from windows to check what certificats are stored in which stores.
// open file
var certificateStore = new CmsSignedData(new
FileStream(_tempFileName.ToString(), FileMode.Open));
// get all certificats
IX509Store x509Certs = certificateStore.GetCertificates("Collection");
var a = new ArrayList(x509Certs.GetMatches(null));
// this works
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
// this doesnt work
// var store = new X509Store("Enterprise Trust", StoreLocation.CurrentUser);
// open store
store.Open(OpenFlags.ReadWrite);
// write in store
foreach (object o in a) {
var cert = (Org.BouncyCastle.X509.X509Certificate)o;
var cert509 = new X509Certificate2();
cert509.Import(cert.GetEncoded());
store.Add(cert509);
}
store.Close();
How do i write correctly in the a store which isnt the StoreName enum?
If you want to be sure you aren't creating a new store, you can use the OpenFlags value OpenExistingOnly. Asserting that flag and checking for, e.g. "Trust2" yields System.Security.Cryptography.CryptographicException: The system cannot find the file specified. Therefore we get the best level of assurance that "Trust" is the right answer by specifying it as:
using (X509Store store = new X509Store("Trust", StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
...
}
(Note that this is less good to use on Linux (.NET Core), because only Windows pre-initializes the stores)
We can get confirmation on the programmatic name to the display name via the certutil.exe command-line utility:
>certutil -store trust
trust "Enterprise Trust"
CertUtil: -store command completed successfully.
(I just don't have anything in that store)
i want to obtain the CN of the certificates stored in the MY store as i want to verify if the certificate exists or not in that store.
I don't know the which method should be used to perform this task.
I tried using below code but it doesn't works
X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName,"Root_Certificate",false);
if(cers.Count>0)
{
//certificate present
}
else
{
//certificate not present
}
Does the subjectName gives CN?
is there any other method?
Please suggest me how to check whether a particular certificate is present or not and i want to do it using CN.
You could use the store.Certificates.Find(X509FindType.FindBySubjectName, "SubjectName", false)
function to search for a certificate by its subject name. Do NOT include "CN=" in the subject name.
To search more specific you could use the thumbprint to search for your certificate.
The following code sample demonstrates this:
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.IncludeArchived);
foreach (var c in store.Certificates)
{
Console.Out.WriteLine(c.Thumbprint);
Console.Out.WriteLine(c.Subject);
}
// Find by thumbprint
X509Certificate2Collection col =
store.Certificates.Find(X509FindType.FindByThumbprint, "669502F7273C447A62550D41CD856665FBF23E48", false);
store.Close();
I've added a foreach loop to the code sample to iterate over all certificates in the selected store.
Your certificate must be listed there. If not, you probably use the wrong store.
Note, there is a My store for the Machine and the Current User. So, be sure to open the right store.
To get the thumbprint of your certificate follow these steps:
Open certmgr.msc.
Double click on your certificate.
Go to the details tab.
Under thumbprint you will find the thumbprint of your certificate.
Hope, this helps.