Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PKCS11 Wrapper #14

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

PKCS11 Wrapper #14

wants to merge 10 commits into from

Conversation

glatigny
Copy link

Continuation of #8

Tested with SoftHSM2 (AES_CBC_PAD & RSA_PKCS keys)

jsmoon-openerd and others added 7 commits June 21, 2024 19:06
Signed-off-by: jsmoon <[email protected]>
Some parts of the code are commented for future improvements.
In case of algorithm which would requires more inputs to know the IV
length, the IsIvNeeded can return "0".
Clearly I'm not very fan of it, but I don't know if giving access to the
PKCS#11 session is better.

Signed-off-by: Jérôme GLATIGNY <[email protected]>
* Moving PKCS11 client into the struct
* Creating the client during configuration loading
* Support of Finalize to destroy the client
* Adding tools functions

Signed-off-by: Jérôme GLATIGNY <[email protected]>
* Move client into a specific file
* Support of setting: token label
* Encrypt do not wrap anymore
* Decrypt do not unwrap anymore
* Encrypt store the key identifier in the KeyInfo
* Decrypt and read the key identifier from the KeyInfo
* Client Encrypt and Decrypt return and take a key identifier
* Pkcs11Key struct to store the key identifier (label and id)

Signed-off-by: Jérôme GLATIGNY <[email protected]>
Signed-off-by: Jérôme GLATIGNY <[email protected]>
Signed-off-by: Jérôme GLATIGNY <[email protected]>
@glatigny glatigny requested a review from cipherboy November 14, 2024 08:08
@cipherboy cipherboy mentioned this pull request Nov 25, 2024
Copy link
Member

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about the delay! This is looking fairly good, thank you! I've left a few comments here and there.

wrappers/pkcs11/pkcs11.go Show resolved Hide resolved
k.keyId = client.GetCurrentKey().String()

// Send a value to test the wrapper and to set the current key id
if _, err := k.Encrypt(context.Background(), []byte("a")); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we think this will always work? For CKM_AES_GCM and CKM_RSA_PKCS_OAEP I think it will, but not for CKM_AES_CBC, which Vault Enterprise supports.

Since this is a fresh start, I suppose we could forbid insecure mechanisms and require AEAD from our mechanism... It is 2024, I don't really see the need to encourage use of CBC any more. Perhaps a note why we think this is fine would be good?

Do you want to take a stab at denying insecure mechanisms? I don't know how we'd do that generally, e.g., if someone wanted to substitute Korean or Japanese algorithms here for instance... I don't really want to maintain the encrypt-then-MAC construction either, tbh.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll remove the encryption test here. The PKCS11 client already test the session.
Regarding the mechanisms, of course AEAD are better.

Something currently wrong in the implementation is the parameter hmac_mechanism is not handled and should be required for CBC and CBC PAD.
At first, I was thinking that SoftHSM2 did not support AES GCM, but it's just the documentation which is not up-to-date.
There is still the issue with YubiHSM2 but yes, it might be interesting to review the list of supported mechanisms !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not inclined to support hmac parameter and just require an AEAD mech.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course.
It just like you wrote 3 days ago: to support encrypt-then-mac it requires to add the HMAC mechanism parameter.

wrapConfig := new(wrapping.WrapperConfig)
wrapConfig.Metadata = make(map[string]string)
wrapConfig.Metadata["lib"] = lib
wrapConfig.Metadata["key_label"] = keyLabel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably know better than I, but I was reading the PKCS#11v3.0 spec and I noticed it very carefully doesn't say CKA_LABEL is guaranteed unique. OpenSC/OpenSC#1613 seems to reference this as a place where it has happened in the wild...

Do we want to allow CKA_UNIQUE_ID as an identifier to compensate?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed the Key Label (CKA_LABEL) and Key ID (CKA_ID) are not unique ; the user is free to choose them (and edit them).
The Unique ID is unique by definition and generated by the Token/HSM itself ; which means that if you import your secret the Unique ID will change.
That's why, I'm not a fan of using the Unique ID in the configuration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why, I'm not a fan of using the Unique ID in the configuration.

Hmm, but if we error like in https://github.com/openbao/go-kms-wrapping/pull/14/files#r1863149975, we're essentially then saying there's no way to use HSMs configured with multiple keys with the same CKA_LABEL and/or CKA_ID values?

I think we're fine with that, but just making sure I understand the implications of this. It seems like we'd prefer a way to disambiguate the keys, given the FindObjects(...) return order may not be deterministic.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Label + ID + KeyType muse be unique yes.
Now, if the ID is not provide during encryption and we found one key for that label, we can retrieve the ID to store it in the metadata, so during the decryption, we will be able to find the right key even if another one has been created with the same label.
If there is more than one key, the fonction FindKey will return an error.

wrappers/pkcs11/pkcs11_client.go Outdated Show resolved Hide resolved
case api.ReadBaoVariable(EnvHsmWrapperKeyId) != "" && !opts.Options.WithDisallowEnvVars:
keyId = api.ReadBaoVariable(EnvHsmWrapperKeyId)
case opts.withKeyId != "":
keyId = opts.withKeyId
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrappers/pkcs11/pkcs11_client.go Outdated Show resolved Hide resolved
if err := c.client.FindObjectsInit(session, template); err != nil {
return nil, fmt.Errorf("failed to pkcs11 FindObjectsInit: %s", err)
}
obj, _, err := c.client.FindObjects(session, 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we want to handle multiple results? I think right now, given the above comment about uniqueness, we're at the mercy of the library as to what order it returns results in, which might be random.

Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a part in Encrypt and Decrypt that I'll move into FindKey: (and I will change the text too).

	if len(obj) != 1 {
		return nil, nil, fmt.Errorf("expected 1 object, got %d", len(obj))
	}

Since we're asking for 2 objects max, we can detect if there is more than 1 and then return an error.

wrappers/pkcs11/pkcs11_client.go Outdated Show resolved Hide resolved
return nil, nil, err
}

var iv []byte
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@glatigny I think you need to correctly handle mechanism parameters here. See https://pkg.go.dev/github.com/miekg/pkcs11#NewGCMParams or https://pkg.go.dev/github.com/miekg/pkcs11#NewOAEPParams.

Otherwise, this fails for me:

    pkcs11_acc_test.go:32: err : failed to pkcs11 EncryptInit: pkcs11: 0x7: CKR_ARGUMENTS_BAD

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up with a Dockerfile:

FROM registry.fedoraproject.org/fedora:latest

COPY . /go-kms-wrapping


# pkcs11-tool --module "$BAO_HSM_LIB" --token-label "$BAO_HSM_TOKEN_LABEL" --so-pin 1234 --pin 4321 --keygen --key-type aes:32 --label "$BAO_HSM_KEY_LABEL" && \

# Set up SoftHSM, generate a key, and run tests.
RUN true && \
    dnf update --refresh -y && \
    dnf install -y softhsm opensc golang && \
    mkdir /softhsm && \
    export SOFTHSM2_CONF=/go-kms-wrapping/wrappers/pkcs11/softhsm2.conf && \
    softhsm2-util --init-token --slot 0 --label "OpenBao g-k-w" --so-pin 1234 --pin 4321 && \
    softhsm2-util --show-slots && \
    export BAO_HSM_TOKEN_LABEL="OpenBao g-k-w" && \
    export BAO_HSM_PIN=4321 && \
    export BAO_HSM_LIB=/usr/lib64/softhsm/libsofthsm.so && \
    export BAO_HSM_KEY_LABEL=bao-root-key && \
    pkcs11-tool --module "$BAO_HSM_LIB" --token-label "$BAO_HSM_TOKEN_LABEL" --so-pin 1234 --pin 4321 --keypairgen --key-type rsa:4096 --label "$BAO_HSM_KEY_LABEL" && \
    pkcs11-tool --module "$BAO_HSM_LIB" --token-label "$BAO_HSM_TOKEN_LABEL" --so-pin 1234 --pin 4321 --show-info && \
    cd /go-kms-wrapping/wrappers/pkcs11 && \
    export VAULT_ACC=true && \
    go test -v ./... && \
    true

with config (wrappers/pkcs11/softhsm2.conf):

directories.tokendir = /softhsm
objectstore.backend = file
log.level = DEBUG

and run via:

$ podman build -f wrappers/pkcs11/Dockerfile .

switch mech {
case pkcs11.CKM_AES_GCM:
return true, 16
case pkcs11.CKM_AES_CBC_PAD:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed my mind, let's deny this mechanism as well if you don't mind and only do CKM_AES_GCM. Otherwise, we'd need to implement an encrypt-then-mac construct with an additional HMAC key.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we should probably implement CKM_AES_CCM; it looks like YubiHSM2 doesn't support GCM mode per https://downloads.yubico.com/support/YubiHSM%20Manual_1_5_0.pdf

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CKM_AES_CCM requires some modifications in the current structure.
jsmoon started with mechanisms which requires an IV while CCM uses a nonce and some other parameters (like the MAC Len).
AAD can also be used with GCM or CCM ; I don't think that we want to use it but, if so, we need to store it.
For the moment, the IV is stored as a prefix ; we add it during encryption and we extract it from the ciphertext during decryption.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad news... It is not possible to add CCM support for the moment.

The PKCS11 lib doesn't have the function to create the CCM parameters:
https://github.com/miekg/pkcs11/blob/master/params.go
but the struct is available in the headers:
https://github.com/miekg/pkcs11/blob/master/pkcs11t.h#L1888
So it needs a small PR to add the wrapping struct and functions.

Copy link
Member

@cipherboy cipherboy Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@glatigny Hmm.... Sadly it kinda looks like miekg/pkcs11 is getting less active. Things seemed to stall after attempting the PKCS#11 v3.0 port: miekg/pkcs11#148 ... Though, I'm sure he'd be welcoming of a PR for CCM support.

I'm not aware of any good alternatives to this library, so perhaps we should start trying to get involved upstream?

That said, I'm fine leaving off CCM for now.

@dangerousd75
Copy link

I am prepared to test this with Thales USB HSM, NitroKey HSM and Yubico 2 HSM in December.
My Entrust Edge will only arrive in February.

* Add rsa_oaep_hash parameter.
* New functions for the creation of PKCS11 encrypt/decrypt parameters.
* FindKey now return only one key handle.

Signed-off-by: Jérôme GLATIGNY <[email protected]>
var params interface{}
switch mechanism {
case pkcs11.CKM_AES_GCM:
params = pkcs11.NewGCMParams(prefix, nil, 128)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, check out this note: https://pkg.go.dev/github.com/miekg/pkcs11#NewGCMParams

Note that some HSMs, like CloudHSM, will ignore the IV you pass in and write their own. As a result, to support all libraries, memory is not freed automatically, so that after the EncryptInit/Encrypt operation the HSM's IV can be read back out. It is up to the caller to ensure that Free() is called on the GCMParams object at an appropriate time, which is after Encrypt/Decrypt. As an example:

.... so we likely want to make sure we save params here and update our copy of prefix from this value before doing the append in Encrypt(...).

* Do not concat the IV with the encrypted content anymore, use the BlobInfo instead.
* Split AES GCM and RSA OAEP into different functions.
* Handle Specific cases for GCM (some HSM don't read the IV/nonce and generate it instead).
* Use the PKCS#11 random generator and propose an interface for it.
* Move constants into a dedicated section.

Signed-off-by: Jérôme GLATIGNY <[email protected]>
@glatigny
Copy link
Author

glatigny commented Dec 4, 2024

Hey @cipherboy
The last commit introduce some refactoring.

I wanted to make the constants a bit cleaner so I moved them into a dedicated section.
At the same time, I changed the IV/Nonce length for AES GCM.

While spliting the Encrypt/Decrypt functions for AES GCM and RSA OAEP, I saw how the IV could be handle by CloudHSM (and maybe others) and also saw that BlobInfo can embed the IV.

I also remove the usage of uuid in order to use the random generator via PKCS#11.
Valut has the "Entropy augmentation seal" feature, the GenerateRandom function could be a start for it.

@glatigny glatigny requested a review from cipherboy December 10, 2024 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants