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

Add PKCS#11 Wrapper #8

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

Conversation

jsmoon-openerd
Copy link

@jsmoon-openerd jsmoon-openerd commented Jun 21, 2024

PKCS#11 currently support RSA, AES encryption algorithm.

splitted from #6 .

Signed-off-by: jsmoon <[email protected]>
@glatigny
Copy link

glatigny commented Jul 3, 2024

Hi and thanks for that PR !

I'm working on a project which requires the usage of an HSM (so PKCS#11) ; thus I want to help and contribute.
To make it short, I retrieved the PR, make few modifications in OpenBAO and tests some cases using SoftHSM2.

For the usage of RSA keys, the initialization is working good.
For AES keys, I found one issue.

First, I made some tests using an AES256 key and I've got CKR_MECHANISM_INVALID error. Switching to AES128 solved the problem.
So I take a look and I saw that it's reading the attribute CKA_VALUE_LEN to compute the ivLength.
But the IV is 16 bytes long for AES-CBC-PAD, so it's not related to the key size.

Maybe it can be interesting to make the PKCS#11 wrapper settings compatible with the Vault wrapper (module => lib, etc)?
Let me know if you need any help (for that PR, validating, implement other algorithms...)

@cipherboy
Copy link
Member

cipherboy commented Jul 25, 2024

\o hey @jsmoon-openerd! Do you mind adding the DCO (--signoff) and force-pushing? And while you're at it, rebasing off of the latest main since we got testing working again? Thanks!

// These constants contain the accepted env vars; the Vault one is for backwards compat
const (
EnvPkcs11WrapperKeyId = "PKCS11_WRAPPER_KEY_ID"
EnvVaultPkcs11SealKeyId = "VAULT_PKCS11_SEAL_KEY_ID"
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to name this BAO_ and use the helper to read the value?

Also, I think per docs this should be called VAULT_HSM_KEY_ID and other environment variables shouldn't be PKCS11_... but instead BAO_HSM_... (which will then be VAULT_ with the helper). :-)


// Map that holds non-sensitive configuration info
wrapConfig := new(wrapping.WrapperConfig)
wrapConfig.Metadata = make(map[string]string)
Copy link
Member

Choose a reason for hiding this comment

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

Module could also go here

// Order of precedence Pkcs11 values:
// * Environment variable
// * Value from Vault configuration file
// * Instance metadata role (access key and secret key)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is true, right?

Under Azure KMS, we see:

        credentialChain := []providers.Provider{
            providers.NewEnvCredentialProvider(),
            providers.NewConfigurationCredentialProvider(credConfig),
            providers.NewInstanceMetadataProvider(),
        }

but we don't see that here, because we don't put PKCS#11 pins in the metadata for the running VM (like Azure puts credentials into the VM's metadata that it can access to communicate with other services).


// EncryptDEK uses the PKCS11 encrypt operation to encrypt the DEK.
func (kms *pkcs11KMS) EncryptDEK(ctx context.Context, plainDEK []byte) ([]byte, error) {
p := pkcs11.New(kms.module)
Copy link
Member

Choose a reason for hiding this comment

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

I'd expect this to be done up in SetConfig(...) and then cached for subsequent calls. I'd also prefer to refactor login out into a separate helper that can be called in multiple places.

}

defer p.Destroy()
defer p.Finalize()
Copy link
Member

Choose a reason for hiding this comment

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

p.Destroy() and p.Finalize() should probably be called from Finalize(...) on the Wrapper, not per-call, otherwise there will be lots and lots of overhead.

I'm not sure that this is thread safe to do this way anyways (loading the module per-call); Wrappers try to be concurrency friendly.

}
defer p.Logout(session)

keyIdBytes, err := hex.DecodeString(kms.keyId)
Copy link
Member

Choose a reason for hiding this comment

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

Per docs I think we need to trim the 0x prefix?

}

// EncryptDEK uses the PKCS11 encrypt operation to encrypt the DEK.
func (kms *pkcs11KMS) EncryptDEK(ctx context.Context, plainDEK []byte) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer we don't call this EncryptDEK -- while perhaps strictly true on some level, I think we want this to be just strictly an Encrypt(...) operation and not something that implies it is a Wrap(...) operation.

wrapConfig.Metadata["kms_key_id"] = k.keyId
wrapConfig.Metadata["slot"] = strconv.Itoa(int(k.client.slot))
if k.client.label != "" {
wrapConfig.Metadata["label"] = k.client.label
Copy link
Member

Choose a reason for hiding this comment

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

Per the docs, I think this actually backwards -- I think label is required, key_id is optional.

// Map that holds non-sensitive configuration info
wrapConfig := new(wrapping.WrapperConfig)
wrapConfig.Metadata = make(map[string]string)
wrapConfig.Metadata["kms_key_id"] = k.keyId
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
wrapConfig.Metadata["kms_key_id"] = k.keyId
wrapConfig.Metadata["key_id"] = k.keyId

For consistency :-)

return nil, fmt.Errorf("given plaintext for encryption is nil")
}

env, err := wrapping.EnvelopeEncrypt(plaintext, opt...)
Copy link
Member

@cipherboy cipherboy Aug 15, 2024

Choose a reason for hiding this comment

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

I don't think we want to do this by default; I'd prefer direct encryption. This will help in the future if we add managed keys to the Transit engine.

For seal purposes, it doesn't matter as the root key is small enough to be directly encrypted.

// This returns the ciphertext, and/or any errors from this
// call. This should be called after the KMS client has been instantiated.
func (k *Wrapper) Encrypt(_ context.Context, plaintext []byte, opt ...wrapping.Option) (*wrapping.BlobInfo, error) {
if plaintext == 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 guess this is the check used elsewhere, but I'd probably prefer:

Suggested change
if plaintext == nil {
if len(plaintext) == 0 {

return k.currentKeyId.Load().(string), nil
}

// Encrypt is used to encrypt the master key using the the PKCS11.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Encrypt is used to encrypt the master key using the the PKCS11.
// Encrypt is used to encrypt data using the the PKCS11 key.

return nil, fmt.Errorf("error decrypting data encryption key: %w", err)
}

envInfo := &wrapping.EnvelopeInfo{
Copy link
Member

Choose a reason for hiding this comment

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

As above.

}
}

func MechanisString(mech uint) string {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func MechanisString(mech uint) string {
func MechanismString(mech uint) string {

}
}

type pkcs11KMS struct {
Copy link
Member

Choose a reason for hiding this comment

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

My structuring comment would be to do what Transit does, and split the low-level PKCS#11 client out from the higher level Wrapper implementation into two separate files. My 2c. :-)

if err != nil {
return nil, fmt.Errorf("failed to decode key id: %w", err)
}
template := []*pkcs11.Attribute{
Copy link
Member

Choose a reason for hiding this comment

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

Keyfinding should probably also be refactored into a common method.

if kms.mechanism != 0 {
mechanism = kms.mechanism
} else {
mechanism = 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 know this is what Vault prefers, but I'd like to see CKM_AES_GCM used instead.

Copy link
Member

Choose a reason for hiding this comment

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

I think we also need more advanced auto-detection of mechanisms, based on the list of supported mechanisms, but that's an issue for another time and I'm happy to take that one on.

if kms.mechanism != 0 {
mechanism = kms.mechanism
} else {
mechanism = pkcs11.CKM_RSA_PKCS
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
mechanism = pkcs11.CKM_RSA_PKCS
mechanism = pkcs11.CKM_RSA_PKCS_OAEP

Vault prefers this.

if kms.mechanism != 0 {
mechanism = kms.mechanism
} else {
mechanism = 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 think we also need more advanced auto-detection of mechanisms, based on the list of supported mechanisms, but that's an issue for another time and I'm happy to take that one on.

@cipherboy
Copy link
Member

Hey @jsmoon-openerd! Sorry, I meant to batch comments but I guess somehow they got submitted in groups rather than all at once (I'd rather have that problem than them disappearing, though!).

The change set is looking fairly good for a first pass, but I've made some commentary about code structuring, compatibility with Vault, and potential issues with PKCS#11 modules.

Let me know if you want me to pick this up for you -- I'll need you to push the DCO sign-off first, but happy to do so if you want. :-)

@cipherboy
Copy link
Member

\o hey @jsmoon-openerd -- just checking in if you're able to do the DCO sign-off. :-) Thanks!

@SorinS
Copy link

SorinS commented Sep 30, 2024

any news on this one guys?

@cipherboy
Copy link
Member

@SorinS Since we don't have signed-off commits, we'll be unable to merge this unless @jsmoon-openerd is able to update them. :-)

So, a clean re-implementation would be ideal as a path forward if that doesn't happen sometime here in the near future.

@SorinS
Copy link

SorinS commented Sep 30, 2024

@SorinS Since we don't have signed-off commits, we'll be unable to merge this unless @jsmoon-openerd is able to update them. :-)

So, a clean re-implementation would be ideal as a path forward if that doesn't happen sometime here in the near future.

Thank you for answering, I'll wait a bit for @jsmoon-openerd then I might take this up. This is an enterprise feature and I am certain it will be needed for unsealing and PKI in large enterprises...

@cipherboy
Copy link
Member

@SorinS I concur, though currently this would just implement auto-unseal with HSMs. Managed keys (as Vault Enterprise calls them) is an entire codebase we don't have, and I'm not sure I'd implement it the same way either tbh.

That said, have you seen our draft roadmap -- if you're interested in doing g-k-w and core work, there's an item in there about using g-k-w plugins from OpenBao which would be cool :-)

@cipherboy
Copy link
Member

@SorinS If you're stil interested in contributing a new, clean-room implementation, I'm happy to review it. :-)

@SorinS
Copy link

SorinS commented Oct 10, 2024

@cipherboy thank you, I will try to develop a new implementation. I am aiming to test on SoftHSM and Luna 7...

@cipherboy
Copy link
Member

cipherboy commented Oct 12, 2024

@SorinS I just looked at the commits again, and only the last commit (fixing CBC Pad support) is not signed off. If you want to base on the previous commit and apply my suggestions above, I think we can use this PR as-is, minus the commit with the CBC Pad fixes... I think most of that will need to be redone anyways based on my feedback above so I think you might end up recreating something different...

(But please, preserve the original attribution on the first two commits!)

@SorinS
Copy link

SorinS commented Oct 13, 2024

@cipherboy thank you, working on it...

@glatigny glatigny mentioned this pull request Nov 13, 2024
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