I'm trying to use Pkcs11Interop to sign a message using the private key from a smart card certificate in a C# application. The smart card we are using contain multiple certificates - usually one is for signing, and one is for authentication. If I were using X509Certificate2, I'd filter certificates based on the X509KeyUsageFlags I'm looking for. I'm struggling to figure out how to approach this using PKCS11.
The code I'm starting with is below. When I call session.FindAllObjects, I'm getting 2 certificates in the result (which is expected, since that is how many certificates are on the smart card.)
I've tried using GetAttributeValue to read various attributes and see if I can use those to identify the correct certificate - strangely, they all return null/0 values. Querying the CKA_SENSITIVE attribute returns True (which is, again, expected), but apparently I cannot read other attributes from the objects.
Am I doing something incorrect in my usage of GetAttributeValue? Or is there some other way I should be approaching this problem?
public byte[] SignMessage(byte[] message, string pin)
{
var factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, DriverPath, AppType.SingleThreaded))
{
ISlot slot = GetSlot(pkcs11Library);
if (slot == null)
{
return null;
}
using (ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, pin);
var searchTemplate = new List<IObjectAttribute> {
factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY),
factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, CKK.CKK_RSA),
factories.ObjectAttributeFactory.Create(CKA.CKA_SIGN, true),
};
List<IObjectHandle> foundObjects = session.FindAllObjects(searchTemplate); // foundObjects.Count = 2!
IObjectHandle privateKey = foundObjects.FirstOrDefault();
var readResult = session.GetAttributeValue(privateKey, new List<CKA>() { CKA.CKA_LABEL });
var label = readResult[0].GetValueAsString(); // label ends up being null!
byte[] result = null;
using (IMechanism signingMechanism = session.Factories.MechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS))
{
result = session.Sign(signingMechanism, privateKey, message);
}
session.DestroyObject(privateKey);
session.Logout();
return result;
}
}
}
I came up with a solution through trial-and-error that seems to function correctly. I'm not sure if this is the correct approach, since it seems quite convoluted, so any feedback would be appreciated. I discovered that the contents of the card include a variety of objects, and a single key pair consists of three objects: a CKO_CERTIFICATE object (which seems to contain the brunt of the metadata about the certificate/keypair), a CKO_PRIVATE_KEY object and a CKO_PUBLIC_KEY object. Each of these has the CKA_ID property populated, and the objects that are part of the same key pair should have the same CKA_ID.
So I built a CertificateWrapper wrapper class to hold references to each of the three objects. I then looped over all objects on the smart card, and built CertificateWrapper objects for each unique key pair.
Then, I was able to construct an X509Certificate2 object using the CKA_VALUE attribute on the CKO_CERTIFICATE object. From there, I was able to build a X509Certificate2Collection object using an array of all of the X509Certificate2 objects I made. I could then use the .Find method (or any other method I wanted) on X509Certificate2Collection to filter down to the particular certificate I was looking for.
Once I had the X509Certificate2 object I was looking for, I was able to map it back to the CertificateWrapper object by matching the serial number from the X509Certificate2 against the CKA_SERIAL_NUMBER attribute from the CKO_CERTIFICATE object. Finally, I was able to use the CKO_PRIVATE_KEY object associated with that CKO_CERTIFICATE to do the signing operation.
Like I said, this seems very round-about, but seemed to allow me to find the correct certificate/key pair I needed for my specific workflow. Hope this explanation might be useful to someone, and I also welcome any feedback on problems with this approach and/or better ways to handle this.