Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion kms/apiv1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ type KeyManager interface {
Close() error
}

// KeyDeleter is an optional interface for KMS implementations that support
// deleting keys.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a later
// release.
type KeyDeleter interface {
DeleteKey(req *DeleteKeyRequest) error
}

// SearchableKeyManager is an optional interface for KMS implementations
// that support searching for keys based on certain attributes.
//
Expand Down Expand Up @@ -54,6 +65,17 @@ type CertificateChainManager interface {
StoreCertificateChain(req *StoreCertificateChainRequest) error
}

// CertificateDeleter is an optional interface for KMS implementations that
// support deleting certificates.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a later
// release.
type CertificateDeleter interface {
DeleteCertificate(req *DeleteCertificateRequest) error
}

// NameValidator is an interface that KeyManager can implement to validate a
// given name or URI.
type NameValidator interface {
Expand Down Expand Up @@ -151,6 +173,9 @@ const (
TPMKMS Type = "tpmkms"
// MacKMS is the KMS implementation using macOS Keychain and Secure Enclave.
MacKMS Type = "mackms"
// PlatformKMS is the KMS implementation that uses TPMKMS on Windows and
// Linux and MacKMS on macOS..
PlatformKMS Type = "kms"
)

// TypeOf returns the type of of the given uri.
Expand Down Expand Up @@ -181,7 +206,7 @@ func (t Type) Validate() error {
return nil
case YubiKey, PKCS11, TPMKMS: // Hardware based kms.
return nil
case SSHAgentKMS, CAPIKMS, MacKMS: // Others
case SSHAgentKMS, CAPIKMS, MacKMS, PlatformKMS: // Others
return nil
}

Expand Down
208 changes: 208 additions & 0 deletions kms/platform/kms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package platform

import (
"context"
"crypto"
"crypto/x509"
"net/url"
"strings"

"go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/uri"
)

const Scheme = "kms"

func init() {
apiv1.Register(apiv1.PlatformKMS, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
return New(ctx, opts)
})
}

const (
backendKey = "backend"
nameKey = "name"
hwKey = "hw"
)

type kmsURI struct {
uri *uri.URI
backend apiv1.Type
name string
hw bool
extraValues url.Values
}

func parseURI(rawuri string) (*kmsURI, error) {
u, err := uri.ParseWithScheme(Scheme, rawuri)
if err != nil {
return nil, err
}

extraValues := make(url.Values)
for k, v := range u.Values {
if k != nameKey && k != hwKey && k != backendKey {
extraValues[k] = v
}
}

return &kmsURI{
uri: u,
backend: apiv1.Type(strings.ToLower(u.Get(backendKey))),
name: u.Get(nameKey),
hw: u.GetBool(hwKey),
extraValues: extraValues,
}, nil
}

type extendedKeyManager interface {
apiv1.KeyManager
apiv1.KeyDeleter
apiv1.CertificateManager
apiv1.CertificateChainManager
apiv1.CertificateDeleter
}

var _ apiv1.KeyManager = (*KMS)(nil)
var _ apiv1.CertificateManager = (*KMS)(nil)
var _ apiv1.CertificateChainManager = (*KMS)(nil)

type KMS struct {
backend extendedKeyManager
transformURI func(*kmsURI) string
}

func New(ctx context.Context, opts apiv1.Options) (*KMS, error) {
return newKMS(ctx, opts)
}

func (k *KMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
name, err := k.transform(req.Name)
if err != nil {
return nil, err
}
return k.backend.GetPublicKey(&apiv1.GetPublicKeyRequest{
Name: name,
})
}

func (k *KMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
name, err := k.transform(req.Name)
if err != nil {
return nil, err
}

req = clone(req)
req.Name = name
return k.backend.CreateKey(req)
}

func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
signingKey, err := k.transform(req.SigningKey)
if err != nil {
return nil, err
}

req = clone(req)
req.SigningKey = signingKey
return k.backend.CreateSigner(req)
}

func (k *KMS) Close() error {
return k.backend.Close()
}

func (k *KMS) DeleteKey(req *apiv1.DeleteKeyRequest) error {
name, err := k.transform(req.Name)
if err != nil {
return err
}

req = clone(req)
req.Name = name
return k.backend.DeleteKey(req)
}

func (k *KMS) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) {
name, err := k.transform(req.Name)
if err != nil {
return nil, err
}

req = clone(req)
req.Name = name
return k.backend.LoadCertificate(req)
}

func (k *KMS) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
name, err := k.transform(req.Name)
if err != nil {
return err
}

req = clone(req)
req.Name = name
return k.backend.StoreCertificate(req)
}

func (k *KMS) LoadCertificateChain(req *apiv1.LoadCertificateChainRequest) ([]*x509.Certificate, error) {
name, err := k.transform(req.Name)
if err != nil {
return nil, err
}

req = clone(req)
req.Name = name
return k.backend.LoadCertificateChain(req)
}

func (k *KMS) StoreCertificateChain(req *apiv1.StoreCertificateChainRequest) error {
name, err := k.transform(req.Name)
if err != nil {
return err
}

req = clone(req)
req.Name = name
return k.backend.StoreCertificateChain(req)
}

func (k *KMS) DeleteCertificate(req *apiv1.DeleteCertificateRequest) error {
name, err := k.transform(req.Name)
if err != nil {
return err
}

req = clone(req)
req.Name = name
return k.backend.DeleteCertificate(req)
}

func (k *KMS) SearchKeys(req *apiv1.SearchKeysRequest) (*apiv1.SearchKeysResponse, error) {
if km, ok := k.backend.(apiv1.SearchableKeyManager); ok {
query, err := k.transform(req.Query)
if err != nil {
return nil, err
}

req = clone(req)
req.Query = query
return km.SearchKeys(req)
}

return nil, apiv1.NotImplementedError{}
}

func (k *KMS) transform(rawuri string) (string, error) {
u, err := parseURI(rawuri)
if err != nil {
return "", err
}

return k.transformURI(u), nil
}

func clone[T any](v *T) *T {
c := *v
return &c
}
65 changes: 65 additions & 0 deletions kms/platform/kms_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package platform

import (
"context"
"fmt"
"maps"
"net/url"

"go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/kms/mackms"
"go.step.sm/crypto/kms/uri"
)

var _ apiv1.SearchableKeyManager = (*KMS)(nil)

func newKMS(ctx context.Context, opts apiv1.Options) (*KMS, error) {
if opts.URI == "" {
return newMacKMS(ctx, opts)
}

u, err := parseURI(opts.URI)
if err != nil {
return nil, err
}

switch u.backend {
case apiv1.TPMKMS:
return newTPMKMS(ctx, opts)
case apiv1.SoftKMS:
return newSoftKMS(ctx, opts)
case apiv1.DefaultKMS, apiv1.MacKMS:
opts.URI = transformToMacKMS(u)
return newMacKMS(ctx, opts)
default:
return nil, fmt.Errorf("failed parsing %q: unsupported backend %q", opts.URI, u.backend)
}
}

func newMacKMS(ctx context.Context, opts apiv1.Options) (*KMS, error) {
km, err := mackms.New(ctx, opts)
if err != nil {
return nil, err
}

return &KMS{
backend: km,
transformURI: transformToMacKMS,
}, nil
}

func transformToMacKMS(u *kmsURI) string {
uv := url.Values{}
if u.name != "" {
uv.Set("label", u.name)
}
if u.hw {
uv.Set("se", "true")
uv.Set("keychain", "dataProtection")
}

// Add custom extra values that might be mackms specific.
maps.Copy(uv, u.extraValues)

return uri.New(mackms.Scheme, uv).String()
}
30 changes: 30 additions & 0 deletions kms/platform/kms_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build !darwin && !windows

package platform

import (
"context"
"fmt"

"go.step.sm/crypto/kms/apiv1"
)

func newKMS(ctx context.Context, opts apiv1.Options) (*KMS, error) {
if opts.URI == "" {
return newTPMKMS(ctx, opts)
}

u, err := parseURI(opts.URI)
if err != nil {
return nil, err
}

switch u.backend {
case apiv1.SoftKMS:
return newSoftKMS(ctx, opts)
case apiv1.DefaultKMS, apiv1.TPMKMS:
return newTPMKMS(ctx, opts)
default:
return nil, fmt.Errorf("failed parsing %q: unsupported backend %q", opts.URI, u.backend)
}
}
Loading