forked from TrueCloudLab/certificates
Add support for cloudkms and softkms.
This commit is contained in:
parent
8a10c5032f
commit
d13754166a
6 changed files with 594 additions and 0 deletions
59
kms/apiv1/options.go
Normal file
59
kms/apiv1/options.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrNotImplemented
|
||||
type ErrNotImplemented struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e ErrNotImplemented) Error() string {
|
||||
if e.msg != "" {
|
||||
return e.msg
|
||||
}
|
||||
return "not implemented"
|
||||
}
|
||||
|
||||
// Type represents the KMS type used.
|
||||
type Type string
|
||||
|
||||
const (
|
||||
// DefaultKMS is a KMS implementation using software.
|
||||
DefaultKMS Type = ""
|
||||
// SoftKMS is a KMS implementation using software.
|
||||
SoftKMS = "softkms"
|
||||
// CloudKMS is a KMS implementation using Google's Cloud KMS.
|
||||
CloudKMS = "cloudkms"
|
||||
// AmazonKMS is a KMS implementation using Amazon AWS KMS.
|
||||
AmazonKMS = "awskms"
|
||||
// PKCS11 is a KMS implementation using the PKCS11 standard.
|
||||
PKCS11 = "pkcs11"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Type string `json:"type"`
|
||||
CredentialsFile string `json:"credentialsFile"`
|
||||
}
|
||||
|
||||
// Validate checks the fields in Options.
|
||||
func (o *Options) Validate() error {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch Type(strings.ToLower(o.Type)) {
|
||||
case DefaultKMS, SoftKMS, CloudKMS:
|
||||
case AmazonKMS:
|
||||
return ErrNotImplemented{"support for AmazonKMS is not yet implemented"}
|
||||
case PKCS11:
|
||||
return ErrNotImplemented{"support for PKCS11 is not yet implemented"}
|
||||
default:
|
||||
return errors.Errorf("unsupported kms type %s", o.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
136
kms/apiv1/requests.go
Normal file
136
kms/apiv1/requests.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type KeyType int
|
||||
|
||||
const (
|
||||
// nolint:camelcase
|
||||
RSA_2048 KeyType = iota
|
||||
RSA_3072
|
||||
RSA_4096
|
||||
EC_P256
|
||||
EC_P384
|
||||
EC_P512
|
||||
)
|
||||
|
||||
// ProtectionLevel specifies on some KMS how cryptographic operations are
|
||||
// performed.
|
||||
type ProtectionLevel int
|
||||
|
||||
const (
|
||||
// Protection level not specified.
|
||||
UnspecifiedProtectionLevel ProtectionLevel = iota
|
||||
// Crypto operations are performed in software.
|
||||
Software
|
||||
// Crypto operations are performed in a Hardware Security Module.
|
||||
HSM
|
||||
)
|
||||
|
||||
// String returns a string representation of p.
|
||||
func (p ProtectionLevel) String() string {
|
||||
switch p {
|
||||
case UnspecifiedProtectionLevel:
|
||||
return "unspecified"
|
||||
case Software:
|
||||
return "software"
|
||||
case HSM:
|
||||
return "hsm"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(%d)", p)
|
||||
}
|
||||
}
|
||||
|
||||
// SignatureAlgorithm used for cryptographic signing.
|
||||
type SignatureAlgorithm int
|
||||
|
||||
const (
|
||||
// Not specified.
|
||||
UnspecifiedSignAlgorithm SignatureAlgorithm = iota
|
||||
// RSASSA-PKCS1-v1_5 key and a SHA256 digest.
|
||||
SHA256WithRSA
|
||||
// RSASSA-PKCS1-v1_5 key and a SHA384 digest.
|
||||
SHA384WithRSA
|
||||
// RSASSA-PKCS1-v1_5 key and a SHA512 digest.
|
||||
SHA512WithRSA
|
||||
// RSASSA-PSS key with a SHA256 digest.
|
||||
SHA256WithRSAPSS
|
||||
// RSASSA-PSS key with a SHA384 digest.
|
||||
SHA384WithRSAPSS
|
||||
// RSASSA-PSS key with a SHA512 digest.
|
||||
SHA512WithRSAPSS
|
||||
// ECDSA on the NIST P-256 curve with a SHA256 digest.
|
||||
ECDSAWithSHA256
|
||||
// ECDSA on the NIST P-384 curve with a SHA384 digest.
|
||||
ECDSAWithSHA384
|
||||
// ECDSA on the NIST P-521 curve with a SHA512 digest.
|
||||
ECDSAWithSHA512
|
||||
// EdDSA on Curve25519 with a SHA512 digest.
|
||||
PureEd25519
|
||||
)
|
||||
|
||||
// String returns a string representation of s.
|
||||
func (s SignatureAlgorithm) String() string {
|
||||
switch s {
|
||||
case UnspecifiedSignAlgorithm:
|
||||
return "unspecified"
|
||||
case SHA256WithRSA:
|
||||
return "SHA256-RSA"
|
||||
case SHA384WithRSA:
|
||||
return "SHA384-RSA"
|
||||
case SHA512WithRSA:
|
||||
return "SHA512-RSA"
|
||||
case SHA256WithRSAPSS:
|
||||
return "SHA256-RSAPSS"
|
||||
case SHA384WithRSAPSS:
|
||||
return "SHA384-RSAPSS"
|
||||
case SHA512WithRSAPSS:
|
||||
return "SHA512-RSAPSS"
|
||||
case ECDSAWithSHA256:
|
||||
return "ECDSA-SHA256"
|
||||
case ECDSAWithSHA384:
|
||||
return "ECDSA-SHA384"
|
||||
case ECDSAWithSHA512:
|
||||
return "ECDSA-SHA512"
|
||||
case PureEd25519:
|
||||
return "Ed25519"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(%d)", s)
|
||||
}
|
||||
}
|
||||
|
||||
type GetPublicKeyRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type GetPublicKeyResponse struct {
|
||||
Name string
|
||||
PublicKey crypto.PublicKey
|
||||
}
|
||||
|
||||
type CreateKeyRequest struct {
|
||||
Parent string
|
||||
Name string
|
||||
Type KeyType
|
||||
Bits int
|
||||
SignatureAlgorithm SignatureAlgorithm
|
||||
|
||||
// ProtectionLevel specifies how cryptographic operations are performed.
|
||||
// Used by: cloudkms
|
||||
ProtectionLevel ProtectionLevel
|
||||
}
|
||||
|
||||
type CreateKeyResponse struct {
|
||||
Name string
|
||||
PublicKey crypto.PublicKey
|
||||
PrivateKey crypto.PrivateKey
|
||||
}
|
||||
|
||||
type CreateSignerRequest struct {
|
||||
SigningKey string
|
||||
SigningKeyPEM []byte
|
||||
Password string
|
||||
}
|
172
kms/cloudkms/cloudkms.go
Normal file
172
kms/cloudkms/cloudkms.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package cloudkms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"time"
|
||||
|
||||
cloudkms "cloud.google.com/go/kms/apiv1"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
"google.golang.org/api/option"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
||||
// protectionLevelMapping maps step protection levels with cloud kms ones.
|
||||
var protectionLevelMapping = map[apiv1.ProtectionLevel]kmspb.ProtectionLevel{
|
||||
apiv1.UnspecifiedProtectionLevel: kmspb.ProtectionLevel_PROTECTION_LEVEL_UNSPECIFIED,
|
||||
apiv1.Software: kmspb.ProtectionLevel_SOFTWARE,
|
||||
apiv1.HSM: kmspb.ProtectionLevel_HSM,
|
||||
}
|
||||
|
||||
// signatureAlgorithmMapping is a mapping between the step signature algorithm,
|
||||
// and bits for RSA keys, with cloud kms one.
|
||||
//
|
||||
// Cloud KMS does not support SHA384WithRSA, SHA384WithRSAPSS, SHA384WithRSAPSS,
|
||||
// ECDSAWithSHA512, and PureEd25519.
|
||||
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]interface{}{
|
||||
apiv1.UnspecifiedSignAlgorithm: kmspb.CryptoKeyVersion_CRYPTO_KEY_VERSION_ALGORITHM_UNSPECIFIED,
|
||||
apiv1.SHA256WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
|
||||
2048: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256,
|
||||
3072: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||
},
|
||||
apiv1.SHA512WithRSA: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256,
|
||||
},
|
||||
apiv1.SHA256WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
|
||||
2048: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256,
|
||||
3072: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256,
|
||||
},
|
||||
apiv1.SHA512WithRSAPSS: map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{
|
||||
0: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512,
|
||||
4096: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512,
|
||||
},
|
||||
apiv1.ECDSAWithSHA256: kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256,
|
||||
apiv1.ECDSAWithSHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384,
|
||||
}
|
||||
|
||||
type keyManagementClient interface {
|
||||
GetPublicKey(context.Context, *kmspb.GetPublicKeyRequest, ...gax.CallOption) (*kmspb.PublicKey, error)
|
||||
AsymmetricSign(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error)
|
||||
CreateCryptoKey(context.Context, *kmspb.CreateCryptoKeyRequest, ...gax.CallOption) (*kmspb.CryptoKey, error)
|
||||
}
|
||||
|
||||
// CloudKMS implements a KMS using Google's Cloud apiv1.
|
||||
type CloudKMS struct {
|
||||
client keyManagementClient
|
||||
}
|
||||
|
||||
func New(ctx context.Context, opts apiv1.Options) (*CloudKMS, error) {
|
||||
var cloudOpts []option.ClientOption
|
||||
if opts.CredentialsFile != "" {
|
||||
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
|
||||
}
|
||||
|
||||
client, err := cloudkms.NewKeyManagementClient(ctx, cloudOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CloudKMS{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateSigner returns a new cloudkms signer configured with the given signing
|
||||
// key name.
|
||||
func (k *CloudKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||
if req.SigningKey == "" {
|
||||
return nil, errors.New("signing key cannot be empty")
|
||||
}
|
||||
|
||||
return newSigner(k.client, req.SigningKey), nil
|
||||
}
|
||||
|
||||
// CreateKey creates in Google's Cloud KMS a new asymmetric key for signing.
|
||||
func (k *CloudKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
switch {
|
||||
case req.Name == "":
|
||||
return nil, errors.New("createKeyRequest 'name' cannot be empty")
|
||||
case req.Parent == "":
|
||||
return nil, errors.New("createKeyRequest 'parent' cannot be empty")
|
||||
}
|
||||
|
||||
protectionLevel, ok := protectionLevelMapping[req.ProtectionLevel]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("cloudKMS does not support protection level '%s'", req.ProtectionLevel)
|
||||
}
|
||||
|
||||
var signatureAlgorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm
|
||||
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s'", req.SignatureAlgorithm)
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm:
|
||||
signatureAlgorithm = v
|
||||
case map[int]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm:
|
||||
if signatureAlgorithm, ok = v[req.Bits]; !ok {
|
||||
return nil, errors.Errorf("cloudKMS does not support signature algorithm '%s' with '%d' bits", req.SignatureAlgorithm, req.Bits)
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected error: this should not happen")
|
||||
}
|
||||
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
response, err := k.client.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{
|
||||
Parent: req.Parent,
|
||||
CryptoKeyId: req.Name,
|
||||
CryptoKey: &kmspb.CryptoKey{
|
||||
Purpose: kmspb.CryptoKey_ASYMMETRIC_SIGN,
|
||||
VersionTemplate: &kmspb.CryptoKeyVersionTemplate{
|
||||
ProtectionLevel: protectionLevel,
|
||||
Algorithm: signatureAlgorithm,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cloudKMS CreateCryptoKey failed")
|
||||
}
|
||||
|
||||
return &apiv1.CreateKeyResponse{
|
||||
Name: response.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPublicKey gets from Google's Cloud KMS a public key by name. Key names
|
||||
// follow the pattern:
|
||||
// projects/([^/]+)/locations/([a-zA-Z0-9_-]{1,63})/keyRings/([a-zA-Z0-9_-]{1,63})/cryptoKeys/([a-zA-Z0-9_-]{1,63})/cryptoKeyVersions/([a-zA-Z0-9_-]{1,63})
|
||||
func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (*apiv1.GetPublicKeyResponse, error) {
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
response, err := k.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cloudKMS GetPublicKey failed")
|
||||
}
|
||||
|
||||
pk, err := pemutil.ParseKey([]byte(response.Pem))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiv1.GetPublicKeyResponse{
|
||||
Name: req.Name,
|
||||
PublicKey: pk,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func defaultContext() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), 15*time.Second)
|
||||
}
|
80
kms/cloudkms/signer.go
Normal file
80
kms/cloudkms/signer.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package cloudkms
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
|
||||
)
|
||||
|
||||
// signer implements a crypto.Signer using Google's Cloud KMS.
|
||||
type signer struct {
|
||||
client keyManagementClient
|
||||
signingKey string
|
||||
}
|
||||
|
||||
func newSigner(c keyManagementClient, signingKey string) *signer {
|
||||
return &signer{
|
||||
client: c,
|
||||
signingKey: signingKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Public returns the public key of this signer or an error.
|
||||
func (s *signer) Public() crypto.PublicKey {
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
response, err := s.client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{
|
||||
Name: s.signingKey,
|
||||
})
|
||||
if err != nil {
|
||||
println(1, err.Error())
|
||||
return errors.Wrap(err, "cloudKMS GetPublicKey failed")
|
||||
}
|
||||
|
||||
pk, err := pemutil.ParseKey([]byte(response.Pem))
|
||||
if err != nil {
|
||||
println(2, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return pk
|
||||
}
|
||||
|
||||
// Sign signs digest with the private key stored in Google's Cloud KMS.
|
||||
func (s *signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
req := &kmspb.AsymmetricSignRequest{
|
||||
Name: s.signingKey,
|
||||
Digest: &kmspb.Digest{},
|
||||
}
|
||||
|
||||
switch h := opts.HashFunc(); h {
|
||||
case crypto.SHA256:
|
||||
req.Digest.Digest = &kmspb.Digest_Sha256{
|
||||
Sha256: digest,
|
||||
}
|
||||
case crypto.SHA384:
|
||||
req.Digest.Digest = &kmspb.Digest_Sha384{
|
||||
Sha384: digest,
|
||||
}
|
||||
case crypto.SHA512:
|
||||
req.Digest.Digest = &kmspb.Digest_Sha512{
|
||||
Sha512: digest,
|
||||
}
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported hash function %v", h)
|
||||
}
|
||||
|
||||
ctx, cancel := defaultContext()
|
||||
defer cancel()
|
||||
|
||||
response, err := s.client.AsymmetricSign(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cloudKMS AsymmetricSign failed")
|
||||
}
|
||||
|
||||
return response.Signature, nil
|
||||
}
|
35
kms/kms.go
Normal file
35
kms/kms.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package kms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/cloudkms"
|
||||
"github.com/smallstep/certificates/kms/softkms"
|
||||
)
|
||||
|
||||
// KeyManager is the interface implemented by all the KMS.
|
||||
type KeyManager interface {
|
||||
GetPublicKey(req *apiv1.GetPublicKeyRequest) (*apiv1.GetPublicKeyResponse, error)
|
||||
CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error)
|
||||
CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error)
|
||||
}
|
||||
|
||||
// New initializes a new KMS from the given type.
|
||||
func New(ctx context.Context, opts apiv1.Options) (KeyManager, error) {
|
||||
if err := opts.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch apiv1.Type(strings.ToLower(opts.Type)) {
|
||||
case apiv1.DefaultKMS, apiv1.SoftKMS:
|
||||
return softkms.New(ctx, opts)
|
||||
case apiv1.CloudKMS:
|
||||
return cloudkms.New(ctx, opts)
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported kms type '%s'", opts.Type)
|
||||
}
|
||||
}
|
112
kms/softkms/softkms.go
Normal file
112
kms/softkms/softkms.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package softkms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/cli/crypto/keys"
|
||||
"github.com/smallstep/cli/crypto/pemutil"
|
||||
)
|
||||
|
||||
type algorithmAttributes struct {
|
||||
Type string
|
||||
Curve string
|
||||
}
|
||||
|
||||
var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes{
|
||||
apiv1.UnspecifiedSignAlgorithm: algorithmAttributes{"EC", "P-256"},
|
||||
apiv1.SHA256WithRSA: algorithmAttributes{"RSA", ""},
|
||||
apiv1.SHA384WithRSA: algorithmAttributes{"RSA", ""},
|
||||
apiv1.SHA512WithRSA: algorithmAttributes{"RSA", ""},
|
||||
apiv1.SHA256WithRSAPSS: algorithmAttributes{"RSA", ""},
|
||||
apiv1.SHA384WithRSAPSS: algorithmAttributes{"RSA", ""},
|
||||
apiv1.SHA512WithRSAPSS: algorithmAttributes{"RSA", ""},
|
||||
apiv1.ECDSAWithSHA256: algorithmAttributes{"EC", "P-256"},
|
||||
apiv1.ECDSAWithSHA384: algorithmAttributes{"EC", "P-384"},
|
||||
apiv1.ECDSAWithSHA512: algorithmAttributes{"EC", "P-521"},
|
||||
apiv1.PureEd25519: algorithmAttributes{"OKP", "Ed25519"},
|
||||
}
|
||||
|
||||
// SoftKSM is a key manager that uses keys stored in disk.
|
||||
type SoftKMS struct{}
|
||||
|
||||
// New returns a new SoftKSM.
|
||||
func New(ctx context.Context, opts apiv1.Options) (*SoftKMS, error) {
|
||||
return &SoftKMS{}, nil
|
||||
}
|
||||
|
||||
// CreateSigner returns a new signer configured with the given signing key.
|
||||
func (k *SoftKMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
||||
var opts []pemutil.Options
|
||||
if req.Password != "" {
|
||||
opts = append(opts, pemutil.WithPassword([]byte(req.Password)))
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(req.SigningKeyPEM) != 0:
|
||||
v, err := pemutil.ParseKey(req.SigningKeyPEM, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, ok := v.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("signingKeyPEM is not a crypto.Signer")
|
||||
}
|
||||
return sig, nil
|
||||
case req.SigningKey != "":
|
||||
v, err := pemutil.Read(req.SigningKey, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, ok := v.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, errors.New("signingKey is not a crypto.Signer")
|
||||
}
|
||||
return sig, nil
|
||||
default:
|
||||
return nil, errors.New("failed to load softKMS: please define signingKeyPEM or signingKey")
|
||||
}
|
||||
}
|
||||
|
||||
func (k *SoftKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
||||
v, ok := signatureAlgorithmMapping[req.SignatureAlgorithm]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("softKMS does not support signature algorithm '%s'", req.SignatureAlgorithm)
|
||||
}
|
||||
|
||||
pub, priv, err := keys.GenerateKeyPair(v.Type, v.Curve, req.Bits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &apiv1.CreateKeyResponse{
|
||||
Name: req.Name,
|
||||
PublicKey: pub,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (*apiv1.GetPublicKeyResponse, error) {
|
||||
v, err := pemutil.Read(req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case *x509.Certificate:
|
||||
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported public key type %T", v)
|
||||
}
|
||||
|
||||
return &apiv1.GetPublicKeyResponse{
|
||||
Name: req.Name,
|
||||
PublicKey: v,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in a new issue