In the previous article, titled "Exploring HSM Interactions: A Journey through SoftHSM" we delved into SoftHSM, pkcs11-tool, and the layers between .NET/Java application code and HSMs. In this article, we will take the next step and learn how to develop a .NET application that communicates with an HSM device. Before proceeding, please make sure to follow the SoftHSM setup guide provided in the previous article.
To begin, we will create a console application called "MyPkcs11App." This application will utilize the NuGet library called "Pkcs11Interop," which provides functionality for interacting with PKCS #11 interfaces. The code will be divided into three sections for easier comprehension.
Creating the .NET Application
In the Program.cs file, we will write the following code:
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
namespace MyPkcs11App
{
public class Program
{
static void Main(string[] args)
{
// Specify the path to unmanaged PKCS#11 library provided by the cryptographic device vendor
string pkcs11LibraryPath = @"/opt/homebrew/Cellar/softhsm/2.6.1/lib/softhsm/libsofthsm2.so";
// Create factories used by Pkcs11Interop library
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
// Load unmanaged PKCS#11 library
using (IPkcs11Library pkcs11Library =
factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, pkcs11LibraryPath,
AppType.MultiThreaded))
{
// Show general information about loaded library
ILibraryInfo libraryInfo = pkcs11Library.GetInfo();
ShowLibraryInfo(libraryInfo);
}
}
private static void ShowLibraryInfo(ILibraryInfo libraryInfo)
{
Console.WriteLine("Library");
Console.WriteLine(" Manufacturer: " + libraryInfo.ManufacturerId);
Console.WriteLine(" Description: " + libraryInfo.LibraryDescription);
Console.WriteLine(" Version: " + libraryInfo.LibraryVersion);
}
}
}
First, we set the path to the PKCS#11 library provided by the device vendor. This library connects our app to the HSM device.
We then create factories for the Pkcs11Interop library, a vital step that enables interaction with the PKCS#11 interfaces. Following this, we load the PKCS#11 library using the defined path "/opt/homebrew/Cellar/softhsm/2.6.1/lib/softhsm/libsofthsm2.so".
After successfully loading the library, your optional step is to extract general information about it. The tool of choice here is the GetInfo() method. To keep your code neat and easily digestible, we've utilized a helper method named ShowLibraryInfo().
Ready to add more functionality to your application? Let's dive into the next wave of code!
// Get list of all available slots
foreach (ISlot slot in pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent))
{
// Show basic information about slot
ISlotInfo slotInfo = slot.GetSlotInfo();
ShowSlotInfo(slotInfo);
if (slotInfo.SlotId == 1870032949)
if (slotInfo.SlotFlags.TokenPresent)
{
// Show basic information about token present in the slot
ITokenInfo tokenInfo = slot.GetTokenInfo();
ShowTokenInfo(tokenInfo);
// Show list of mechanisms (algorithms) supported by the token
Console.WriteLine("Supported mechanisms: ");
foreach (CKM mechanism in slot.GetMechanismList())
Console.WriteLine(" " + mechanism);
}
}
Now, let's add helper methods:
private static void ShowSlotInfo(ISlotInfo slotInfo)
{
Console.WriteLine();
Console.WriteLine("Slot");
Console.WriteLine(" Manufacturer: " + slotInfo.ManufacturerId);
Console.WriteLine(" Description: " + slotInfo.SlotDescription);
Console.WriteLine(" Token present: " + slotInfo.SlotFlags.TokenPresent);
}
private static void ShowTokenInfo(ITokenInfo tokenInfo)
{
Console.WriteLine("Token");
Console.WriteLine(" Manufacturer: " + tokenInfo.ManufacturerId);
Console.WriteLine(" Model: " + tokenInfo.Model);
Console.WriteLine(" Serial number: " + tokenInfo.SerialNumber);
Console.WriteLine(" Label: " + tokenInfo.Label);
}
Awesome!
We've now enhanced our main method's functionality. Upon gaining general facts about the loaded library, we proceed to extract the entire list of available slots via the GetSlotList() method, courtesy of pkcs11Library.
We find out basic details about each slot on the list using the GetSlotInfo() method and then present it with our ShowSlotInfo() helper method.
We ensure each slot matches a specific slot ID (1870032949 was the slot id we initialized in our previous article), as our focus is not to examine every slot.
Finally, we try to attain token information and also the array of mechanisms (algorithms) that the token supports.
The output should be something like:
Library
Manufacturer: SoftHSM
Description: Implementation of PKCS11
Version: 2.6
Slot
Manufacturer: SoftHSM project
Description: SoftHSM slot ID 0x6f767035
Token present: True
Token
Manufacturer: SoftHSM project
Model: SoftHSM v2
Serial number: 5ba5e8f56f767035
Label: MyToken
Supported mechanisms:
CKM_AES_CBC
CKM_AES_CBC_ENCRYPT_DATA
CKM_AES_CBC_PAD
CKM_AES_CMAC
CKM_AES_CTR
CKM_AES_ECB
CKM_AES_ECB_ENCRYPT_DATA
CKM_AES_GCM
CKM_AES_KEY_GEN
CKM_AES_KEY_WRAP
CKM_AES_KEY_WRAP_PAD
CKM_DES2_KEY_.....
Having familiarized yourself with the supported mechanisms, let's dive into trying one out on the subsequent portion of code. First off, we'll start with opening a session. This is typically initiated by logging in with the appropriate user credentials. It's worth noting that in most cases, applications will lean towards user privileges, rather than security officer (SO) privileges.
Adding Functionality to the Application
Let's generate an AES key and name it"NoraTechSecretKey." To accomplish this, we will create a supporting method called "GenerateAesKey" Let's proceed with the implementation:
using (ISession session = slot.OpenSession(SessionType.ReadWrite))
{
string normalUserPin = "1234";
session.Login(CKU.CKU_USER, ConvertUtils.Utf8StringToBytes(normalUserPin));
var secretKey = GenerateAesKey(session,"NoraTechSecretKey");
}
Create GenerateAesKey method, and specify it's attributes:
private static IObjectHandle GenerateAesKey(ISession session, string keyLabel)
{
// Prepare attribute template of new key
DateTime endTime = DateTime.UtcNow.AddDays(31);
List<IObjectAttribute> objectAttributes = new List<IObjectAttribute>();
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, keyLabel));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, (uint)CKO.CKO_SECRET_KEY));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_KEY_TYPE, (uint)CKK.CKK_AES));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VALUE_LEN, 32));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ENCRYPT, true));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_DECRYPT, true));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_DERIVE, true));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_EXTRACTABLE, true));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_END_DATE, endTime));
objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_MODIFIABLE, true));
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_AES_KEY_GEN);
return session.GenerateKey(mechanism, objectAttributes);
//session.Logout();
}
Within GenerateAesKey, key attributes are outlined in great detail. Attributes such as type, size, label, and time undergo specific configurations. To delve deeper into the meanings of CKU,CKA,CKM,CKO values, you can navigate to the OASIS PKCS #11 Technical Committee website. Here, you'll find the PKCS #11 specification and an array of related resources: https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=pkcs11
Now, Go ahead and run the code. Once the key is generated, you might wonder how to check it, right?
Bring our handy tool, pkcs-11, back into the picture. Just follow this simple command:
pkcs11-tool --module /opt/homebrew/Cellar/softhsm/2.6.1/lib/softhsm/libsofthsm2.so --slot 1870032949 --login --pin <user-pin> --list-objects
You should expect to see something similar to this:
VALUE: b361f9356eb25c04440e42ae1668f20715ded5988f69e5ca9c2877a523236fd5
label: NoraTechSecretKey
Usage: encrypt, decrypt, verify, wrap, unwrap, derive
Access: extractable, local
Secret Key Object; AES length 32
Bravo! You've uncovered our secret key!
Next up, let's explore some encryption. First let's define IV paramaeter :
static byte[] iv ={ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 };
Within the scope of the session block, our task is to identify a piece of plaintext. Following this, we'll proceed to encrypt it, effectively creating a cipher.
Once encrypted, we would then decrypt it and determine if the resulting value is identical to the original plaintext.
byte[] plaintext = ConvertUtils.Utf8StringToBytes("Our new password is P@$$w0rd");
Console.WriteLine($"Plaintext is:{ConvertUtils.BytesToUtf8String(plaintext)}\"");
var cipher = Encrypt(session, plaintext,secretKey);
Console.WriteLine($"Cipher is:\"{ConvertUtils.BytesToUtf8String(cipher)}\"");
var decryptedValue = Decrypt(session,cipher,secretKey);
Console.WriteLine($"Decryption Value is:\" {ConvertUtils.BytesToUtf8String(decryptedValue)}\"");
//optional , delete the key if you dont need it
session.DestroyObject(secretKey);
And its supporting Encrypt and Decrypt methods :
private static byte[] Encrypt(ISession session, byte[] plaintext, IObjectHandle secretKey)
{
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_AES_CBC_PAD, iv);
var cipher= session.Encrypt(mechanism, secretKey, plaintext);
return cipher;
}
private static byte[] Decrypt(ISession session, byte[] cipher, IObjectHandle secretKey)
{
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_AES_CBC_PAD, iv);
var plaintext= session.Decrypt(mechanism, secretKey, cipher);
return plaintext;
}
Ok, Let’s execute the code, If things go as planned, you should stumble upon lines in the output that resemble the following:
Plaintext is:Our new password is P@$$w0rd"
Cipher is:"��r�Xd�W�����ut���
�<�����"
Decryption Value is:" Our new password is P@$$w0rd"
Ok, well done !
In conclusion, we have explored the process of building a .NET application for HSM communication. By utilizing the Pkcs11Interop library, we were able to establish a connection with the HSM, retrieve essential information, and generate an AES secret key.
I hope that this article has been helpful ♡
Comments