forked from TrueCloudLab/certificates
2d85d4c1c1
A server without TLS was added to serve the SCEP endpoints. According to the RFC, SCEP has to be served via HTTP. The `sscep` client, for example, will stop any URL that does not start with `http://` from being used, so serving SCEP seems to be the right way to do it. This commit adds a second server for which no TLS configuration is configured. A distinct field in the configuration, `insecureAddress` was added to specify the address for the insecure server. The SCEP endpoints will also still be served via HTTPS. Some clients may be able to work with that. This commit also improves how the crypto.Decrypter interface is handled for the different types of KMSes supported by step. The apiv1.Decrypter interface was added. Currently only SoftKMS implements this interface, providing a crypto.Decrypter required for SCEP operations.
354 lines
10 KiB
Go
354 lines
10 KiB
Go
// +build cgo
|
|
|
|
package pkcs11
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/elliptic"
|
|
"crypto/x509"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/ThalesIgnite/crypto11"
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/certificates/kms/apiv1"
|
|
"github.com/smallstep/certificates/kms/uri"
|
|
)
|
|
|
|
// Scheme is the scheme used in uris.
|
|
const Scheme = "pkcs11"
|
|
|
|
// DefaultRSASize is the number of bits of a new RSA key if no size has been
|
|
// specified.
|
|
const DefaultRSASize = 3072
|
|
|
|
// P11 defines the methods on crypto11.Context that this package will use. This
|
|
// interface will be used for unit testing.
|
|
type P11 interface {
|
|
FindKeyPair(id, label []byte) (crypto11.Signer, error)
|
|
FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error)
|
|
ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error
|
|
DeleteCertificate(id, label []byte, serial *big.Int) error
|
|
GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error)
|
|
GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error)
|
|
Close() error
|
|
}
|
|
|
|
var p11Configure = func(config *crypto11.Config) (P11, error) {
|
|
return crypto11.Configure(config)
|
|
}
|
|
|
|
// PKCS11 is the implementation of a KMS using the PKCS #11 standard.
|
|
type PKCS11 struct {
|
|
p11 P11
|
|
closed sync.Once
|
|
}
|
|
|
|
// New returns a new PKCS11 KMS.
|
|
func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) {
|
|
var config crypto11.Config
|
|
if opts.URI != "" {
|
|
u, err := uri.ParseWithScheme(Scheme, opts.URI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.Pin = u.Pin()
|
|
config.Path = u.Get("module-path")
|
|
config.TokenLabel = u.Get("token")
|
|
config.TokenSerial = u.Get("serial")
|
|
if v := u.Get("slot-id"); v != "" {
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid")
|
|
}
|
|
config.SlotNumber = &n
|
|
}
|
|
}
|
|
if config.Pin == "" && opts.Pin != "" {
|
|
config.Pin = opts.Pin
|
|
}
|
|
|
|
switch {
|
|
case config.Path == "":
|
|
return nil, errors.New("kms uri 'module-path' are required")
|
|
case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil:
|
|
return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required")
|
|
case config.Pin == "":
|
|
return nil, errors.New("kms 'pin' cannot be empty")
|
|
case config.TokenLabel != "" && config.TokenSerial != "":
|
|
return nil, errors.New("kms uri 'token' and 'serial' are mutually exclusive")
|
|
case config.TokenLabel != "" && config.SlotNumber != nil:
|
|
return nil, errors.New("kms uri 'token' and 'slot-id' are mutually exclusive")
|
|
case config.TokenSerial != "" && config.SlotNumber != nil:
|
|
return nil, errors.New("kms uri 'serial' and 'slot-id' are mutually exclusive")
|
|
}
|
|
|
|
p11, err := p11Configure(&config)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error initializing PKCS#11")
|
|
}
|
|
|
|
return &PKCS11{
|
|
p11: p11,
|
|
}, nil
|
|
}
|
|
|
|
func init() {
|
|
apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) {
|
|
return New(ctx, opts)
|
|
})
|
|
}
|
|
|
|
// GetPublicKey returns the public key ....
|
|
func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
|
|
if req.Name == "" {
|
|
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
|
|
}
|
|
|
|
signer, err := findSigner(k.p11, req.Name)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getPublicKey failed")
|
|
}
|
|
|
|
return signer.Public(), nil
|
|
}
|
|
|
|
// CreateKey generates a new key in the PKCS#11 module and returns the public key.
|
|
func (k *PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyResponse, error) {
|
|
switch {
|
|
case req.Name == "":
|
|
return nil, errors.New("createKeyRequest 'name' cannot be empty")
|
|
case req.Bits < 0:
|
|
return nil, errors.New("createKeyRequest 'bits' cannot be negative")
|
|
}
|
|
|
|
signer, err := generateKey(k.p11, req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "createKey failed")
|
|
}
|
|
|
|
return &apiv1.CreateKeyResponse{
|
|
Name: req.Name,
|
|
PublicKey: signer.Public(),
|
|
CreateSignerRequest: apiv1.CreateSignerRequest{
|
|
SigningKey: req.Name,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// CreateSigner creates a signer using the key present in the PKCS#11 MODULE signature
|
|
// slot.
|
|
func (k *PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error) {
|
|
switch {
|
|
case req.SigningKey == "":
|
|
return nil, errors.New("createSignerRequest 'signingKey' cannot be empty")
|
|
}
|
|
|
|
signer, err := findSigner(k.p11, req.SigningKey)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "createSigner failed")
|
|
}
|
|
|
|
return signer, nil
|
|
}
|
|
|
|
// LoadCertificate implements kms.CertificateManager and loads a certificate
|
|
// from the YubiKey.
|
|
func (k *PKCS11) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certificate, error) {
|
|
if req.Name == "" {
|
|
return nil, errors.New("loadCertificateRequest 'name' cannot be nil")
|
|
}
|
|
cert, err := findCertificate(k.p11, req.Name)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "loadCertificate failed")
|
|
}
|
|
return cert, nil
|
|
}
|
|
|
|
// StoreCertificate implements kms.CertificateManager and stores a certificate
|
|
// in the YubiKey.
|
|
func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
|
|
switch {
|
|
case req.Name == "":
|
|
return errors.New("storeCertificateRequest 'name' cannot be empty")
|
|
case req.Certificate == nil:
|
|
return errors.New("storeCertificateRequest 'Certificate' cannot be nil")
|
|
}
|
|
|
|
id, object, err := parseObject(req.Name)
|
|
if err != nil {
|
|
return errors.Wrap(err, "storeCertificate failed")
|
|
}
|
|
|
|
cert, err := k.p11.FindCertificate(id, object, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "storeCertificate failed")
|
|
}
|
|
if cert != nil {
|
|
return errors.Wrap(apiv1.ErrAlreadyExists{
|
|
Message: req.Name + " already exists",
|
|
}, "storeCertificate failed")
|
|
}
|
|
|
|
if err := k.p11.ImportCertificateWithLabel(id, object, req.Certificate); err != nil {
|
|
return errors.Wrap(err, "storeCertificate failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteKey is a utility function to delete a key given an uri.
|
|
func (k *PKCS11) DeleteKey(uri string) error {
|
|
id, object, err := parseObject(uri)
|
|
if err != nil {
|
|
return errors.Wrap(err, "deleteKey failed")
|
|
}
|
|
signer, err := k.p11.FindKeyPair(id, object)
|
|
if err != nil {
|
|
return errors.Wrap(err, "deleteKey failed")
|
|
}
|
|
if signer == nil {
|
|
return nil
|
|
}
|
|
if err := signer.Delete(); err != nil {
|
|
return errors.Wrap(err, "deleteKey failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteCertificate is a utility function to delete a certificate given an uri.
|
|
func (k *PKCS11) DeleteCertificate(uri string) error {
|
|
id, object, err := parseObject(uri)
|
|
if err != nil {
|
|
return errors.Wrap(err, "deleteCertificate failed")
|
|
}
|
|
if err := k.p11.DeleteCertificate(id, object, nil); err != nil {
|
|
return errors.Wrap(err, "deleteCertificate failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close releases the connection to the PKCS#11 module.
|
|
func (k *PKCS11) Close() (err error) {
|
|
k.closed.Do(func() {
|
|
err = errors.Wrap(k.p11.Close(), "error closing pkcs#11 context")
|
|
})
|
|
return
|
|
}
|
|
|
|
func toByte(s string) []byte {
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
return []byte(s)
|
|
}
|
|
|
|
func parseObject(rawuri string) ([]byte, []byte, error) {
|
|
u, err := uri.ParseWithScheme(Scheme, rawuri)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
id := u.GetEncoded("id")
|
|
object := u.Get("object")
|
|
if len(id) == 0 && object == "" {
|
|
return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri)
|
|
}
|
|
|
|
return id, toByte(object), nil
|
|
}
|
|
|
|
func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) {
|
|
id, object, err := parseObject(req.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signer, err := ctx.FindKeyPair(id, object)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if signer != nil {
|
|
return nil, apiv1.ErrAlreadyExists{
|
|
Message: req.Name + " already exists",
|
|
}
|
|
}
|
|
|
|
// Enforce the use of both id and labels. This is not strictly necessary in
|
|
// PKCS #11, but it's a good practice.
|
|
if len(id) == 0 || len(object) == 0 {
|
|
return nil, errors.Errorf("key with uri %s is not valid, id and object are required", req.Name)
|
|
}
|
|
|
|
bits := req.Bits
|
|
if bits == 0 {
|
|
bits = DefaultRSASize
|
|
}
|
|
|
|
switch req.SignatureAlgorithm {
|
|
case apiv1.UnspecifiedSignAlgorithm:
|
|
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
|
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
|
|
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
|
|
case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
|
|
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
|
|
case apiv1.ECDSAWithSHA256:
|
|
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
|
case apiv1.ECDSAWithSHA384:
|
|
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P384())
|
|
case apiv1.ECDSAWithSHA512:
|
|
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P521())
|
|
case apiv1.PureEd25519:
|
|
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
|
default:
|
|
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
|
}
|
|
}
|
|
|
|
func findSigner(ctx P11, rawuri string) (crypto11.Signer, error) {
|
|
id, object, err := parseObject(rawuri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signer, err := ctx.FindKeyPair(id, object)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error finding key with uri %s", rawuri)
|
|
}
|
|
if signer == nil {
|
|
return nil, errors.Errorf("key with uri %s not found", rawuri)
|
|
}
|
|
return signer, nil
|
|
}
|
|
|
|
func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) {
|
|
u, err := uri.ParseWithScheme(Scheme, rawuri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
id, object, serial := u.GetEncoded("id"), u.Get("object"), u.Get("serial")
|
|
if len(id) == 0 && object == "" && serial == "" {
|
|
return nil, errors.Errorf("key with uri %s is not valid, id, object or serial are required", rawuri)
|
|
}
|
|
|
|
var serialNumber *big.Int
|
|
if serial != "" {
|
|
b, err := hex.DecodeString(serial)
|
|
if err != nil {
|
|
return nil, errors.Errorf("key with uri %s is not valid, failed to decode serial", rawuri)
|
|
}
|
|
serialNumber = new(big.Int).SetBytes(b)
|
|
}
|
|
|
|
cert, err := ctx.FindCertificate(id, toByte(object), serialNumber)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error finding certificate with uri %s", rawuri)
|
|
}
|
|
if cert == nil {
|
|
return nil, errors.Errorf("certificate with uri %s not found", rawuri)
|
|
}
|
|
return cert, nil
|
|
}
|