Install SoftHSMv2 and use it via OpenSSL and PKCS #11

SoftHSM is an implementation of a cryptographic store accessible through a PKCS #11 interface. You can use it to explore PKCS #11 without having a hardware security module. It is being developed as a part of the OpenDNSSEC project. SoftHSM uses Botan for its cryptographic operations.

The basics

What is a hardware security module or HSM? A hardware security module is a secure physical device designed to generate, store, and protect digital, high-value cryptographic keys. It is a secure crypto-processor that often comes in the form of a plug-in card with built-in tamper protection.

Due to the critical role they play in securing applications and infrastructure, HSMs and/or the cryptographic modules are typically certified to internationally recognized standards such as Common Criteria or FIPS 140 to provide users with independent assurance that the design and implementation of the product and cryptographic algorithms are sound. The highest level of FIPS 140 security certification attainable is Security Level 4 (Overall). When used in financial payments applications, the security of an HSM is often validated against the HSM requirements defined by the Payment Card Industry Security Standards Council.

When we talk about HSM, we often hear about FIPS 140. Developed by the National Institute of Standards and Technology (NIST), Federal Information Processing Standards are used by United States government agencies and contractors in non-military computer systems. FIPS 140 series comprises the U.S. government computer security standards that define requirements for cryptography modules, including both hardware and software components. The current version is FIPS 140-2.

FIPS 140 enforces strong cryptographic algorithms, provides good physical security, and requires power-on self tests to ensure a device is still in compliance before operating. FIPS 140-2 evaluation is required of vendors that sell products implementing cryptography to the federal government, and the financial industry is increasingly specifying FIPS 140-2 as a procurement requirement.

A hardware security module can be employed in any application that uses digital keys. Typically the keys would be of high value - meaning there would be a significant, negative impact to the owner of the key if it were compromised. Here are the sectors in which we often deal with HSMs:

What is PKCS #11? In cryptography, PKCS #11 is one of the Public-Key Cryptography Standards, and also refers to the programming interface to create and manipulate cryptographic tokens (a token where the secret is a cryptographic key). The PKCS #11 standard defines a platform-independent API to cryptographic tokens, such as hardware security modules and smart cards, and names the API itself “Cryptoki” (from “cryptographic token interface” and pronounced as “crypto-key”, although “PKCS #11” is often used to refer to the API as well as the standard that defines it).

According to its name, SoftHSM is a contradiction in terms? Yes, that’s right! SoftHSM cryptography is done software only! Looking at the source code everything its based on OpenSSL library calls only. There is PIN and PUK implemented, but as keys are stored in files and not stored in hardware.

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

sudo dnf -y update

Install development tools

sudo dnf -y groupinstall "Development Tools"

Install required packages

sudo dnf -y install opensc openssl-devel vim

Download SoftHSMv2

Check the latest available version here.

curl -LOsSf https://dist.opendnssec.org/source/softhsm-2.6.1.tar.gz

Extract source code

tar -xzf softhsm-2.6.1.tar.gz

Increase the maximum locked memory

For optimal performance and increased security of SoftHSMv2, it is recommended to increase the maximum size of memory that a process can “lock” in RAM to prevent it from swapping.

First, to known this value, you can execute the following command.

ulimit -l
64

In my case, a process can lock maximum 64 KB of memory. This parameter should be set to unlimited.

To do this, open the /etc/security/limits.conf file and add the following line just after #<domain> <type> <item> <value>.

*               -       memlock         unlimited

You must log out and log back in to see this value changed.

ulimit -l
unlimited

Compile and install the library

OpenSSL 1.1.0 and later no longer include the GOST engine, so you must use the --disable-gost flag when configuring.

cd softhsm-2.6.1
./configure --disable-gost
make
sudo make install

Check if everything is correctly installed

Shared library

find /usr/local/lib -type f -iname "libsofthsm2.so" -exec file {} \; 2> /dev/null
/usr/local/lib/softhsm/libsofthsm2.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=da3860cb692a67ad45be066d86ecfd97f0c0ec3d, with debug_info, not stripped

Looks good for the shared library.

Binaries

find /usr/local/bin/ -type f -iname "softhsm2-*" 2> /dev/null
/usr/local/bin/softhsm2-keyconv
/usr/local/bin/softhsm2-util
/usr/local/bin/softhsm2-dump-file

Looks good for the binaries.

Configure token

You can use either SoftHSMv2 binaries (here softhsm2-util) or the PKCS #11 interface. The SO PIN can be used to re-initialize the token and the User PIN is handed out to the application so it can interact with the token.

List all the available slots

softhsm2-util --show-slots
Available slots:
Slot 0
    Slot info:
        Description:      SoftHSM slot ID 0x0
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:
        Initialized:      no
        User PIN init.:   no
        Label:

As you can see, the slot 0 is not initialized Initialized: no and User PIN init.: no.

Initialize token

You can use the flag --free to use the first free/uninitialized token. You can define the label you want. Type in SO PIN and User PIN. Once a token has been initialized, more slots will be added automatically with a new uninitialized token.

softhsm2-util --init-token --free --label "Test token"
Slot 0 has a free/uninitialized token.
=== SO PIN (4-255 characters) ===
Please enter SO PIN: *********
Please reenter SO PIN: *********
=== User PIN (4-255 characters) ===
Please enter user PIN: ******
Please reenter user PIN: ******
The token has been initialized and is reassigned to slot 31331403

Check

Our token seems to be initialized, we can check it with this command.

softhsm2-util --show-slots
Available slots:
Slot 31331403
    Slot info:
        Description:      SoftHSM slot ID 0x1de144b
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:    2987ba2681de144b
        Initialized:      yes
        User PIN init.:   yes
        Label:            Test token
Slot 1
    Slot info:
        Description:      SoftHSM slot ID 0x1
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:
        Initialized:      no
        User PIN init.:   no
        Label:

You can create another token by executing again the --init-token command.

How to use SoftHSMv2

Now that you have a slot at your disposal, you can create your very first RSA key pair! There are two ways to do this. The first is to use the pkcs11-tool, an utility for managing and using PKCS #11 security tokens. The second is to use a PKCS11 #11 implementation in a language where it is available: Java, Python, C, C++, Rust, Golang… I will show you both ways. Concerning the second way, I will use the Golang programming language.

pkcs11-tool

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --keypairgen --usage-sign --usage-decrypt --key-type rsa:4096 --label RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Key pair generated:
Private Key Object; RSA
  label:      RSA4096SD
  Usage:      decrypt, sign, unwrap
  Access:     sensitive, always sensitive, never extractable, local
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local

Flags explanation:

The private key, for obvious reasons, can never be extracted. However, you can extract your public key if you need it.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --read-object --type pubkey --label RSA4096SD --output-file pubkey.der

Flags explanation:

I specified .der for the output file type because when you extract an object it is a Distinguished Encoding Rules (DER) encoded in ASN.1.

You can validate it by using an OpenSSL command.

openssl asn1parse -in pubkey.der -inform DER
    0:d=0  hl=4 l= 546 cons: SEQUENCE
    4:d=1  hl=2 l=  13 cons: SEQUENCE
    6:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
   17:d=2  hl=2 l=   0 prim: NULL
   19:d=1  hl=4 l= 527 prim: BIT STRING

You can also have another view with the dumpasn1 tool.

dumpasn1 -d pubkey.der
  0 546: SEQUENCE {
  4  13: . SEQUENCE {
  6   9: . . OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 17   0: . . NULL
       : . . }
 19 527: . BIT STRING, encapsulates {
 24 522: . . SEQUENCE {
 28 513: . . . INTEGER
       : . . . . 00 D7 58 76 E4 75 82 72 23 D4 0E 24 6C 4F F9 BB
       : . . . . 40 B7 11 48 5D 2C 9A 8A 31 62 84 0C CF C1 B2 7D
       : . . . . 35 DA 7D B3 41 DC 63 E5 5B 0E DB 17 CE 92 0B BF
       : . . . . 9E 7E 7D 6C D6 3A C4 7C 57 7B 10 26 D8 07 40 6B
       : . . . . 56 1B 3D AF 7F FD F5 7A 68 19 08 54 55 28 D4 5D
       : . . . . 77 FF C6 1D 66 27 AF 40 82 AB 3C E4 8E 99 3B 81
       : . . . . A3 42 BB 6D 0E B1 AB 1F 64 C9 8C 5F 63 ED B5 0C
       : . . . . FF 91 70 F2 B3 6C 08 8F 82 93 F3 AA 26 FF 7B 6C
       : . . . . . . . . [ Another 385 bytes skipped ]
545   3: . . . INTEGER 65537
       : . . . }
       : . . }
       : . }

0 warnings, 0 errors.

Or transform the public key to PEM format. A .pem file is just a Base64 encoded .der file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -text -noout
RSA Public-Key: (4096 bit)
Modulus:
    00:d7:58:76:e4:75:82:72:23:d4:0e:24:6c:4f:f9:
    bb:40:b7:11:48:5d:2c:9a:8a:31:62:84:0c:cf:c1:
    b2:7d:35:da:7d:b3:41:dc:63:e5:5b:0e:db:17:ce:
    92:0b:bf:9e:7e:7d:6c:d6:3a:c4:7c:57:7b:10:26:
    d8:07:40:6b:56:1b:3d:af:7f:fd:f5:7a:68:19:08:
    54:55:28:d4:5d:77:ff:c6:1d:66:27:af:40:82:ab:
    3c:e4:8e:99:3b:81:a3:42:bb:6d:0e:b1:ab:1f:64:
    c9:8c:5f:63:ed:b5:0c:ff:91:70:f2:b3:6c:08:8f:
    82:93:f3:aa:26:ff:7b:6c:cf:b0:84:3b:b5:af:91:
    14:3e:f3:0a:6b:80:6f:b5:44:78:6b:06:3e:a3:bd:
    4d:be:72:09:2d:b5:50:29:9b:07:e7:bf:35:be:01:
    b4:1c:ac:c7:31:a8:95:a2:98:c7:2f:36:1e:d5:8f:
    66:9f:fe:ab:09:a5:92:5f:69:3e:79:8a:2e:ea:90:
    06:53:fe:28:e6:86:c2:50:97:ef:f1:13:4c:d9:ff:
    d5:c4:98:32:e1:f1:c0:9a:68:02:e9:e9:c7:98:f5:
    76:9b:9b:21:d2:14:2f:14:93:6c:49:5b:ca:36:ce:
    09:ed:52:cc:5c:bc:85:56:53:54:58:79:35:6f:1c:
    ce:4e:1d:25:80:da:1f:20:c0:ac:1b:6a:ad:d0:ec:
    57:5c:8a:f9:4b:4a:55:30:ee:5c:40:af:3d:12:fc:
    72:11:12:09:1a:dc:2b:03:83:6b:03:5c:7d:e3:32:
    d9:0b:73:a9:61:73:5e:e9:33:b7:c5:c5:4f:29:7b:
    e0:10:45:ab:4b:20:6a:40:32:62:8b:b6:17:7c:9a:
    9b:ea:ac:7e:39:49:a7:1a:62:08:b7:db:a4:5a:1e:
    b0:53:c6:94:26:77:fa:e1:59:60:4c:3a:f2:11:05:
    69:58:5d:cd:5b:f9:1c:32:5a:3a:5f:54:34:24:7b:
    fa:d1:66:42:be:1e:41:10:92:0b:e7:92:6b:06:ec:
    05:e4:bb:69:9f:50:1c:72:22:35:e3:ca:d3:1d:2a:
    72:f9:32:9f:ea:08:ba:f9:b6:ff:7f:54:c0:d0:b9:
    fb:d6:c0:f4:c8:3a:a9:05:74:ee:cc:28:49:c4:28:
    f0:65:e2:75:cb:0c:e4:d6:96:f6:05:53:1e:3e:3d:
    02:c3:6c:fe:64:f1:02:ce:ee:a7:5b:29:f6:05:2a:
    d4:17:64:4d:64:17:15:3d:14:fd:9f:66:c5:31:26:
    e5:ef:02:7a:0b:16:03:41:96:61:e3:96:28:64:cd:
    05:80:3c:2b:16:a9:bb:65:52:e7:93:6f:c8:0f:c4:
    27:1a:75
Exponent: 65537 (0x10001)

Cipher

First, you must convert your .der file in a .pem file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -out pubkey.pem
writing RSA key

Then, you can cipher a file.

echo "Hello, world!" | tee -a hello.txt
Hello, world!
openssl rsautl -encrypt -inkey pubkey.pem -in hello.txt -pubin -out hello.txt.ciphered

Decipher

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --decrypt --input-file hello.txt.chiphered --mechanism RSA-PKCS
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using decrypt algorithm RSA-PKCS
Hello, world!

Flags are quite easy to understand. Concerning the --mechanism RSA-PKCS flag, this is the PKCS #1 RSA mechanism, denoted CKM_RSA_PKCS. It’s a multi-purpose mechanism based on the RSA public-key cryptosystem and the block formats defined in PKCS #1. It supports single-part encryption and decryption, single-part signatures and verification with and without message recovery, key wrapping, and key unwrapping. This mechanism corresponds only to the part of PKCS #1 that involves RSA; it does not compute a message digest or a DigestInfo encoding as specified for the md2withRSAEncryption and md5withRSAEncryption algorithms in PKCS #1.

We recover our Hello, World!.

Sign

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --sign --mechanism SHA512-RSA-PKCS-PSS --input-file hello.txt.ciphered --output-file hello.txt.sig
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using signature algorithm SHA512-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA512, mgf=MGF1-SHA512, salt_len=64 B

Once again, flags are quite easy to understand. Concerning the --machanism SHA512-RSA-PKCS1-PSS flag, denoted CKM_SHA512_RSA_PKCS_PSS perform the same operations using the SHA-512 hash functions.

Verify

openssl dgst -keyform DER -verify pubkey.der -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:auto -signature hello.txt.sig hello.txt.ciphered
Verified OK

Hash

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --sign --mechanism SHA384-RSA-PKCS --input-file hello.txt.ciphered --output-file hello.txt.sig.hash
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using signature algorithm SHA384-RSA-PKCS
openssl rsautl -verify -inkey pubkey.pem -in hello.sig.hash -pubin | openssl asn1parse -inform DER
    0:d=0  hl=2 l=  65 cons: SEQUENCE
    2:d=1  hl=2 l=  13 cons: SEQUENCE
    4:d=2  hl=2 l=   9 prim: OBJECT            :sha384
   15:d=2  hl=2 l=   0 prim: NULL
   17:d=1  hl=2 l=  48 prim: OCTET STRING      [HEX DUMP]:79A7AEC70847C242102B889B298A0720803B340B350CD9E89F574C684D46BFB232B92D2DF356FD77E4D2047C43B3F8A0
sha384sum hello.txt
79a7aec70847c242102b889b298a0720803b340b350cd9e89f574c684d46bfb232b92d2df356fd77e4d2047c43b3f8a0  hello.txt

Pretty easy, isn’t it?

Before proceeding to the next step, I suggest that you delete the keys and files that have been created to avoid any ambiguity.

Delete the private key

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --delete-object --type privkey -a RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:

Delete the public key

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --delete-object --type pubkey -a RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:

Remove files

rm -f hello.*

PKCS #11 implementation in Golang

I like Golang and we are lucky, a PKCS #11 implementation is available in this language. The first thing to do is to install Golang.

Install Golang

curl -LOsSf https://go.dev/dl/go1.18.1.linux-amd64.tar.gz
sudo tar -xzf go1.18.1.linux-amd64.tar.gz -C /usr/local

Add the following lines to ~/.bash_profile.

PATH=$PATH:/usr/local/go/bin
export PATH

Source your ~/.bash_profile file.

source ~/.bash_profile

Check.

go version
go version go1.18.1 linux/amd64

Create the Go’s directories.

mkdir -p ~/go/{pkg,src,bin}

Create the folder where our code will be located.

mkdir -p ~/go/src/playground
cd $_

Initialize the Go’s mod to write our code.

go mod init playground
go: creating new go.mod: module playground

Download the library.

go get github.com/miekg/pkcs11
go: downloading github.com/miekg/pkcs11 v1.1.1
go get: added github.com/miekg/pkcs11 v1.1.1

Generate RSA key pair

Create a file called main.go and add the following content. I’ll let you browse the comments to understand what the code does.

In the main() function, call the different functions, one after the other and in order.

func main() {
        generateRSAKeyPair(31331403)
        // cipherData(31331403, "Hello, World!")
        // decipherData(31331403)
        // signData(31331403)
        // verifySignedData(31331403)
}

Also, don’t forget to add the two functions below that manage the retrieval of available slots.

// getAvailableSlots retrieves available slots and return them
func getAvailableSlots(module p11.Module) []p11.Slot {
        slots, err := module.Slots()
        if err != nil {
                log.Fatalln(err)
        }
        return slots
}

// findRequestedSlot finds the requested slot given in parameter and return its instance if exists
func findRequestedSlot(module p11.Module, slotID uint) *p11.Slot {
        slots := getAvailableSlots(module)

        var slot *p11.Slot

        found := false

        // Loop through the available slots returned by the function getAvailableSlots
        for _, availableSlot := range slots {
                if availableSlot.ID() == slotID {
                        // Fill the structure
                        slot = &availableSlot
                        found = true
                        break
                }
        }

        if !found {
                log.Fatalf("Failed to find slot with ID %d\n", slotID)
        }

        return slot
}
// generateRSAKeyPairRequest generates a request to generate an RSA key pair and returns a p11.GenerateKeyPairRequest
func generateRSAKeyPairRequest(objectLabel string) p11.GenerateKeyPairRequest {
        // Define the mechanism to generate an RSA key pair
        mechanism := *pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil)

        publicKeyTemplate := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
                // The type of the key
                pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
                // The CKA_TOKEN attribute identifies whether the object is a token object or a session object
                pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
                // The CKA_VERIFY attribute of the verification key, which indicates whether the key supports verification where the signature is an appendix to the data
                pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
                // The CKA_ENCRYPT attribute of the encryption key, which indicates whether the key supports encryption
                pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
                // Public exponent e
                pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
                // Key length
                pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, 4096),
                // The CKA_LABEL attribute is intended to assist users in browsing
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
        }

        privateKeyTemplate := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
                pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
                pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
                // The CKA_DECRYPT attribute of the decryption key, which indicates whether the key supports decryption
                pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
                // When the CKA_PRIVATE attribute is CK_TRUE (true), a user may not access the object until the user has been authenticated to the token.
                pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
                // The CKA_EXTRACTABLE attribute is CK_FALSE (false), then certain attributes of the secret key cannot be revealed in plaintext outside the token
                pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
        }

        // Return the request with the different information
        return p11.GenerateKeyPairRequest{
                Mechanism:            mechanism,
                PublicKeyAttributes:  publicKeyTemplate,
                PrivateKeyAttributes: privateKeyTemplate,
        }
}

// generateRSAKeyPair generates an RSA key pair based on a request
func generateRSAKeyPair(slotID uint) {
        // Load the shared library
        module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
        defer module.Destroy()
        if err != nil {
                log.Fatalln(err)
        }

        // Check if the slot ID given in parameter is available
        slot := findRequestedSlot(module, slotID)

        // Open a RW session
        session, err := slot.OpenWriteSession()
        if err != nil {
                log.Fatalln(err)
        }

        // Login to the token, 123456 is the User PIN defined during the command `softhsm2-util --init-token --free --label "Test token"`
        if err := session.Login("123456"); err != nil {
                log.Fatalln(err)
        }

        // Generate the RSA key pair with label `RSA4096SD`
        request := generateRSAKeyPairRequest("RSA4096SD")
        _, err = session.GenerateKeyPair(request)
        if err != nil {
                log.Fatalln(err)
        }

        log.Println("RSA key pair successfully generated")
}
go run main.go
2022/01/31 10:43:26 RSA key pair successfully generated

You have just created your RSA key via PKCS #11! You can check if it is in your token with a pkcs11-tool command.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --list-objects
Using slot 0 with a present token (0x1de144b)
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local

It’s right there, it’s the one with the RSA4096SD label.

Cipher

// cipherData ciphers data with a public key
func cipherData(slotID uint, data string) {
        // Load the shared library
        module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
        defer module.Destroy()
        if err != nil {
                log.Fatalln(err)
        }

        // Check if the slot ID given in parameter is available
        slot := findRequestedSlot(module, slotID)

        // Open a RO session
        session, err := slot.OpenSession()
        if err != nil {
                log.Fatalln(err)
        }

        // Create a template to find the public key
        template := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, "RSA4096SD"),
        }

        // Find the object based on template
        publicKeyObject, err := session.FindObject(template)
        if err != nil {
                log.Fatalln(err)
        }

        // Cast the p11.Object to p11.PublicKey
        publicKey := p11.PublicKey(publicKeyObject)

        // Define the mechanism to cipher and decipher
        mechanism := *pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)

        // Cipher the message
        cipheredData, err := publicKey.Encrypt(mechanism, []byte(data))
        if err != nil {
                log.Fatalln(err)
        }

        // Write the ciphered data to a file
        if err := os.WriteFile("hello.txt.ciphered", cipheredData, 0644); err != nil {
                log.Fatalln(err)
        }

        fmt.Println("Ciphered data written into hello.txt.ciphered")
}
go run main.go
Ciphered data written into hello.txt.ciphered

Decipher

// decipherData deciphers data with a private key
func decipherData(slotID uint) {
        // Load the shared library
        module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
        defer module.Destroy()
        if err != nil {
                log.Fatalln(err)
        }

        // Check if the slot ID given in parameter is available
        slot := findRequestedSlot(module, slotID)

        // Open a RO session
        session, err := slot.OpenSession()

        // Login to the token, 123456 is the User PIN defined during the command `softhsm2-util --init-token --free --label "Test token"`
        if err := session.Login("123456"); err != nil {
                log.Fatalln(err)
        }

        // Create a template to find the private key
        template := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, "RSA4096SD"),
        }

        // Find the object based on template
        privateKeyObject, err := session.FindObject(template)
        if err != nil {
                log.Fatalln(err)
        }

        // Cast the p11.Object to p11.PrivateKey
        privateKey := p11.PrivateKey(privateKeyObject)

        // Define the mechanism to cipher and decipher
        mechanism := *pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)

        // Read the ciphered data
        data, err := os.ReadFile("./hello.txt.ciphered")
        if err != nil {
                log.Fatalln(err)
        }

        // Decipher the data
        decipheredMessage, err := privateKey.Decrypt(mechanism, data)
        if err != nil {
                log.Fatalln(err)
        }

        fmt.Println(string(decipheredMessage))
}
go run main.go
Hello, World!

Sign

// signData signs data with a private key
func signData(slotID uint) {
        // Load the shared library
        module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
        defer module.Destroy()
        if err != nil {
                log.Fatalln(err)
        }

        // Check if the slot ID given in parameter is available
        slot := findRequestedSlot(module, slotID)

        // Open a RO session
        session, err := slot.OpenSession()

        // Login to the token, 123456 is the User PIN defined during the command `softhsm2-util --init-token --free --label "Test token"`
        if err := session.Login("123456"); err != nil {
                log.Fatalln(err)
        }

        // Create a template to find the private key
        template := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, "RSA4096SD"),
        }

        // Find the object based on template
        privateKeyObject, err := session.FindObject(template)
        if err != nil {
                log.Fatalln(err)
        }

        // Cast the p11.Object to p11.PrivateKey
        privateKey := p11.PrivateKey(privateKeyObject)

        // Define the mechanism to sign/verify
        mechanism := *pkcs11.NewMechanism(pkcs11.CKM_SHA384_RSA_PKCS, nil)

        // Read the ciphered data
        data, err := os.ReadFile("./hello.txt.ciphered")
        if err != nil {
                log.Fatalln(err)
        }

        // Sign data
        signedData, err := privateKey.Sign(mechanism, data)
        if err != nil {
                log.Fatalln(err)
        }

        // Write the signed data to a file
        if err := os.WriteFile("hello.txt.sig", signedData, 0644); err != nil {
                log.Fatalln(err)
        }

        fmt.Println("Signed data written into hello.txt.sig")
}
go run main.go
Signed data written into hello.txt.sig

Verify

// verifySignedData verifies signed data with a public key
func verifySignedData(slotID uint) {
        // Load the shared library
        module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
        defer module.Destroy()
        if err != nil {
                log.Fatalln(err)
        }

        // Check if the slot ID given in parameter is available
        slot := findRequestedSlot(module, slotID)

        // Open a RO session
        session, err := slot.OpenSession()

        // Create a template to find the public key
        template := []*pkcs11.Attribute{
                pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
                pkcs11.NewAttribute(pkcs11.CKA_LABEL, "RSA4096SD"),
        }

        // Find the object based on template
        publicKeyObject, err := session.FindObject(template)
        if err != nil {
                log.Fatalln(err)
        }

        // Cast the p11.Object to p11.PublicKey
        publicKey := p11.PublicKey(publicKeyObject)

        // Define the mechanism to sign/verify
        mechanism := *pkcs11.NewMechanism(pkcs11.CKM_SHA384_RSA_PKCS, nil)

        // Read the unsigned data
        data, err := os.ReadFile("hello.txt.ciphered")
        if err != nil {
                log.Fatalln(err)
        }

        // Read the signed data
        signedData, err := os.ReadFile("hello.txt.sig")
        if err != nil {
                log.Fatalln(err)
        }

        // Verify signature
        if err := publicKey.Verify(mechanism, data, signedData); err != nil {
                log.Fatalln(err)
        }

        fmt.Println("Verification OK")
}
go run main.go
Verification OK

Concerning the signature, you have noticed that the mechanism used is CKM_SHA256_RSA_PKCS. Mechanims CKM_SHA256_RSA_PKCS does following things:

To see the hash, in the same way as before, we need the public key, so we have to extract it.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --read-object --type pubkey --label RSA4096SD --output-file pubkey.der
Using slot 0 with a present token (0x1de144b)

Next, you must convert your .der file in a .pem file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -out pubkey.pem
writing RSA key

Then, you can use rsautl and asn1parse to examine the ASN.1 structure.

openssl rsautl -verify -inkey pubkey.pem -in hello.txt.sig -pubin | openssl asn1parse -inform DER
    0:d=0  hl=2 l=  65 cons: SEQUENCE
    2:d=1  hl=2 l=  13 cons: SEQUENCE
    4:d=2  hl=2 l=   9 prim: OBJECT            :sha384
   15:d=2  hl=2 l=   0 prim: NULL
   17:d=1  hl=2 l=  48 prim: OCTET STRING      [HEX DUMP]:03B002CE15E3A77C82455A04EFF8CD542DF031626517B795172BEF8C3AAA9B4A01ACE766DA4ECA47AF4958D5BA5A6553

By performing a simple sha384sum on the signed file (hello.txt.ciphered), we get the same hash value.

sha384sum hello.txt.ciphered
03b002ce15e3a77c82455a04eff8cd542df031626517b795172bef8c3aaa9b4a01ace766da4eca47af4958d5ba5a6553  hello.txt.ciphered

That’s all for this article. We have seen how to compile and install SoftHSMv2 and interact with it via basic OpenSSL commands and going a bit further by using a PKCS #11 implementation. We have of course only scratched the surface of this specification, there are many interesting things that can be done and that will most certainly be in another article.