Add non-TLS server and improve crypto.Decrypter interface

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.
This commit is contained in:
Herman Slatman 2021-03-12 14:18:36 +01:00 committed by max furman
parent e7cb80f880
commit 2d85d4c1c1
7 changed files with 38 additions and 42 deletions

View file

@ -23,6 +23,7 @@ import (
casapi "github.com/smallstep/certificates/cas/apiv1" casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/kms" "github.com/smallstep/certificates/kms"
"github.com/smallstep/certificates/kms/apiv1"
kmsapi "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/kms/sshagentkms"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
@ -314,13 +315,14 @@ func (a *Authority) init() error {
} }
// TODO: decide if this is a good approach for providing the SCEP functionality // TODO: decide if this is a good approach for providing the SCEP functionality
// It currently mirrors the logic for the x509CAServer
if a.scepService == nil { if a.scepService == nil {
var options casapi.Options var options casapi.Options
if a.config.AuthorityConfig.Options != nil { if a.config.AuthorityConfig.Options != nil {
options = *a.config.AuthorityConfig.Options options = *a.config.AuthorityConfig.Options
} }
// Read intermediate and create X509 signer for default CAS. // Read intermediate and create X509 signer and decrypter for default CAS.
if options.Is(casapi.SoftCAS) { if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil { if err != nil {
@ -333,12 +335,15 @@ func (a *Authority) init() error {
if err != nil { if err != nil {
return err return err
} }
options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey, if km, ok := a.keyManager.(apiv1.Decrypter); ok {
Password: []byte(a.config.Password), options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
}) DecryptionKey: a.config.IntermediateKey,
if err != nil { Password: []byte(a.config.Password),
return err })
if err != nil {
return err
}
} }
} }
@ -481,11 +486,6 @@ func (a *Authority) init() error {
audiences := a.config.GetAudiences() audiences := a.config.GetAudiences()
a.provisioners = provisioner.NewCollection(audiences) a.provisioners = provisioner.NewCollection(audiences)
config := provisioner.Config{ config := provisioner.Config{
// TODO: I'm not sure if extending this configuration is a good way to integrate
// It's powerful, but leaks quite some seemingly internal stuff to the provisioner.
// IntermediateCert: a.config.IntermediateCert,
// SigningKey: a.config.IntermediateKey,
// CACertificates: a.rootX509Certs,
Claims: claimer.Claims(), Claims: claimer.Claims(),
Audiences: audiences, Audiences: audiences,
DB: a.db, DB: a.db,
@ -495,6 +495,14 @@ func (a *Authority) init() error {
}, },
GetIdentityFunc: a.getIdentityFunc, GetIdentityFunc: a.getIdentityFunc,
} }
// Check if a KMS with decryption capability is required and available
if a.requiresDecrypter() {
if _, ok := a.keyManager.(apiv1.Decrypter); !ok {
return errors.New("keymanager doesn't provide crypto.Decrypter")
}
}
// Store all the provisioners // Store all the provisioners
for _, p := range a.config.AuthorityConfig.Provisioners { for _, p := range a.config.AuthorityConfig.Provisioners {
if err := p.Init(config); err != nil { if err := p.Init(config); err != nil {
@ -569,6 +577,20 @@ func (a *Authority) CloseForReload() {
} }
} }
// requiresDecrypter iterates over the configured provisioners
// and determines if the Authority requires a KMS that provides
// a crypto.Decrypter by implementing the apiv1.Decrypter
// interface. Currently only the SCEP provider requires this,
// but others may be added in the future.
func (a *Authority) requiresDecrypter() bool {
for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeSCEP {
return true
}
}
return false
}
// GetSCEPService returns the configured SCEP Service // GetSCEPService returns the configured SCEP Service
// TODO: this function is intended to exist temporarily // TODO: this function is intended to exist temporarily
// in order to make SCEP work more easily. It can be // in order to make SCEP work more easily. It can be

View file

@ -13,10 +13,13 @@ type KeyManager interface {
GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error)
CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error)
CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error)
CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface?
Close() error Close() error
} }
type Decrypter interface {
CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error)
}
// CertificateManager is the interface implemented by the KMS that can load and // CertificateManager is the interface implemented by the KMS that can load and
// store x509.Certificates. // store x509.Certificates.
type CertificateManager interface { type CertificateManager interface {

View file

@ -3,7 +3,6 @@ package awskms
import ( import (
"context" "context"
"crypto" "crypto"
"fmt"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -222,11 +221,6 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error
return NewSigner(k.service, req.SigningKey) return NewSigner(k.service, req.SigningKey)
} }
// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS
func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// Close closes the connection of the KMS client. // Close closes the connection of the KMS client.
func (k *KMS) Close() error { func (k *KMS) Close() error {
return nil return nil

View file

@ -3,7 +3,6 @@ package cloudkms
import ( import (
"context" "context"
"crypto" "crypto"
"fmt"
"log" "log"
"strings" "strings"
"time" "time"
@ -285,11 +284,6 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe
return pk, nil return pk, nil
} }
// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS
func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// getPublicKeyWithRetries retries the request if the error is // getPublicKeyWithRetries retries the request if the error is
// FailedPrecondition, caused because the key is in the PENDING_GENERATION // FailedPrecondition, caused because the key is in the PENDING_GENERATION
// status. // status.

View file

@ -352,8 +352,3 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) {
} }
return cert, nil return cert, nil
} }
// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11
func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}

View file

@ -7,7 +7,6 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"fmt"
"io" "io"
"net" "net"
"os" "os"
@ -205,8 +204,3 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi
return nil, errors.Errorf("unsupported public key type %T", v) return nil, errors.Errorf("unsupported public key type %T", v)
} }
} }
// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent
func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}

View file

@ -7,7 +7,6 @@ import (
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"fmt"
"net/url" "net/url"
"strings" "strings"
@ -190,11 +189,6 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e
return signer, nil return signer, nil
} }
// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey
func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) {
return nil, fmt.Errorf("not implemented yet")
}
// Close releases the connection to the YubiKey. // Close releases the connection to the YubiKey.
func (k *YubiKey) Close() error { func (k *YubiKey) Close() error {
return errors.Wrap(k.yk.Close(), "error closing yubikey") return errors.Wrap(k.yk.Close(), "error closing yubikey")