Skip to content

Commit

Permalink
Start implementing signing function for HC vault (spiffe#5058)
Browse files Browse the repository at this point in the history
  • Loading branch information
InverseIntegral committed Aug 18, 2024
1 parent 4a137ce commit 2a3de21
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 1 deletion.
84 changes: 83 additions & 1 deletion pkg/server/plugin/keymanager/hashicorpvault/hashicorp_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/hcl"
Expand Down Expand Up @@ -186,7 +187,41 @@ func (p *Plugin) GenerateKey(ctx context.Context, req *keymanagerv1.GenerateKeyR
}

func (p *Plugin) SignData(ctx context.Context, req *keymanagerv1.SignDataRequest) (*keymanagerv1.SignDataResponse, error) {
return nil, errors.New("sign data is not implemented")
if req.KeyId == "" {
return nil, status.Error(codes.InvalidArgument, "key id is required")
}
if req.SignerOpts == nil {
return nil, status.Error(codes.InvalidArgument, "signer opts is required")
}

p.mu.RLock()
defer p.mu.RUnlock()

keyEntry, hasKey := p.entries[req.KeyId]
if !hasKey {
return nil, status.Errorf(codes.NotFound, "key %q not found", req.KeyId)
}

hashAlgo, signingAlgo, err := algosForKMS(keyEntry.PublicKey.Type, req.SignerOpts)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

err = p.genVaultClient()
if err != nil {
return nil, err
}

signResp, err := p.vc.SignData(ctx, req.KeyId, req.Data, hashAlgo, signingAlgo)

if err != nil {
return nil, status.Errorf(codes.Internal, "failed to sign: %v", err)
}

return &keymanagerv1.SignDataResponse{
Signature: signResp.Data["signature"].([]byte),
KeyFingerprint: keyEntry.PublicKey.Fingerprint,
}, nil
}

func (p *Plugin) GetPublicKey(_ context.Context, req *keymanagerv1.GetPublicKeyRequest) (*keymanagerv1.GetPublicKeyResponse, error) {
Expand Down Expand Up @@ -222,6 +257,53 @@ func (p *Plugin) GetPublicKeys(context.Context, *keymanagerv1.GetPublicKeysReque
return &keymanagerv1.GetPublicKeysResponse{PublicKeys: keys}, nil
}

func algosForKMS(keyType keymanagerv1.KeyType, signerOpts any) (TransitHashAlgorithm, TransitSignatureAlgorithm, error) {
var (
hashAlgo keymanagerv1.HashAlgorithm
isPSS bool
)

switch opts := signerOpts.(type) {
case *keymanagerv1.SignDataRequest_HashAlgorithm:
hashAlgo = opts.HashAlgorithm
isPSS = false
case *keymanagerv1.SignDataRequest_PssOptions:
if opts.PssOptions == nil {
return "", "", errors.New("PSS options are required")
}
hashAlgo = opts.PssOptions.HashAlgorithm
isPSS = true
// opts.PssOptions.SaltLength is handled by Vault. The salt length matches the bits of the hashing algorithm.
default:
return "", "", fmt.Errorf("unsupported signer opts type %T", opts)
}

isRSA := keyType == keymanagerv1.KeyType_RSA_2048 || keyType == keymanagerv1.KeyType_RSA_4096

switch {
case hashAlgo == keymanagerv1.HashAlgorithm_UNSPECIFIED_HASH_ALGORITHM:
return "", "", errors.New("hash algorithm is required")
case keyType == keymanagerv1.KeyType_EC_P256 && hashAlgo == keymanagerv1.HashAlgorithm_SHA256:
return TransitHashAlgorithmSHA256, TransitSignatureSignatureAlgorithmPKCS1v15, nil
case keyType == keymanagerv1.KeyType_EC_P384 && hashAlgo == keymanagerv1.HashAlgorithm_SHA384:
return TransitHashAlgorithmSHA384, TransitSignatureSignatureAlgorithmPKCS1v15, nil
case isRSA && !isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA256:
return TransitHashAlgorithmSHA256, TransitSignatureSignatureAlgorithmPKCS1v15, nil
case isRSA && !isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA384:
return TransitHashAlgorithmSHA384, TransitSignatureSignatureAlgorithmPKCS1v15, nil
case isRSA && !isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA512:
return TransitHashAlgorithmSHA512, TransitSignatureSignatureAlgorithmPKCS1v15, nil
case isRSA && isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA256:
return TransitHashAlgorithmSHA256, TransitSignatureSignatureAlgorithmPSS, nil
case isRSA && isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA384:
return TransitHashAlgorithmSHA384, TransitSignatureSignatureAlgorithmPSS, nil
case isRSA && isPSS && hashAlgo == keymanagerv1.HashAlgorithm_SHA512:
return TransitHashAlgorithmSHA512, TransitSignatureSignatureAlgorithmPSS, nil
default:
return "", "", fmt.Errorf("unsupported combination of keytype: %v and hashing algorithm: %v", keyType, hashAlgo)
}
}

func (p *Plugin) createKey(ctx context.Context, spireKeyID string, keyType keymanagerv1.KeyType) (*keyEntry, error) {
err := p.genVaultClient()
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions pkg/server/plugin/keymanager/hashicorpvault/vault_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,21 @@ const (
TransitKeyType_ECDSA_P384 TransitKeyType = "ecdsa-p384"
)

type TransitHashAlgorithm string

const (
TransitHashAlgorithmSHA256 TransitHashAlgorithm = "sha2-256"
TransitHashAlgorithmSHA384 TransitHashAlgorithm = "sha2-384"
TransitHashAlgorithmSHA512 TransitHashAlgorithm = "sha2-512"
)

type TransitSignatureAlgorithm string

const (
TransitSignatureSignatureAlgorithmPSS TransitSignatureAlgorithm = "pss"
TransitSignatureSignatureAlgorithmPKCS1v15 TransitSignatureAlgorithm = "pkcs1v15"
)

// CreateKey creates a new key in the specified transit secret engine
// See: https://developer.hashicorp.com/vault/api-docs/secret/transit#create-key
func (c *Client) CreateKey(ctx context.Context, spireKeyID string, keyType TransitKeyType) (*vapi.Secret, error) {
Expand All @@ -371,3 +386,16 @@ func (c *Client) GetKey(ctx context.Context, spireKeyID string) (*vapi.Secret, e
// TODO: Make the transit engine path configurable
return c.vaultClient.Logical().ReadWithContext(ctx, fmt.Sprintf("/transit/keys/%s", spireKeyID))
}

func (c *Client) SignData(ctx context.Context, spireKeyID string, data []byte, hashAlgo TransitHashAlgorithm, signatureAlgo TransitSignatureAlgorithm) (*vapi.Secret, error) {
// TODO: Handle errors here
// TODO: Make the transit engine path configurable
return c.vaultClient.Logical().WriteWithContext(ctx, fmt.Sprintf("/transit/sign/%s", spireKeyID), map[string]interface{}{
"key_version": "0",
"hash_algorithm": hashAlgo,
"input": data,
"signature_algorithm": signatureAlgo,
"marshalling_algorithm": "asn1", // TODO: Should this be jwt?
"salt_length": "hash", // TODO: Should this be auto or should we let it be configured?
})
}

0 comments on commit 2a3de21

Please sign in to comment.