Address (most) PR comments

This commit is contained in:
Herman Slatman 2021-03-21 16:42:41 +01:00
parent a526065d0c
commit 583d60dc0d
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
12 changed files with 127 additions and 216 deletions

View file

@ -353,34 +353,29 @@ func (a *Authority) init() error {
// TODO: decide if this is a good approach for providing the SCEP functionality
// It currently mirrors the logic for the x509CAService
if a.requiresSCEPService() && a.scepService == nil {
var options casapi.Options
if a.config.AuthorityConfig.Options != nil {
options = *a.config.AuthorityConfig.Options
}
var options scep.Options
// Read intermediate and create X509 signer and decrypter for default CAS.
if options.Is(casapi.SoftCAS) {
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
if km, ok := a.keyManager.(kmsapi.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
if km, ok := a.keyManager.(kmsapi.Decrypter); ok {
options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{
DecryptionKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
}
}
a.scepService, err = scep.NewService(context.Background(), options)

View file

@ -102,9 +102,3 @@ func (s *SCEP) GetChallengePassword() string {
func (s *SCEP) GetCapabilities() []string {
return s.Capabilities
}
// Interface guards
var (
_ Interface = (*SCEP)(nil)
//_ scep.Provisioner = (*SCEP)(nil)
)

View file

@ -151,11 +151,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
scepPrefix := "scep"
scepAuthority, err := scep.New(auth, scep.AuthorityOptions{
Service: auth.GetSCEPService(),
Backdate: *config.AuthorityConfig.Backdate,
DB: auth.GetDatabase().(nosql.DB),
DNS: dns,
Prefix: scepPrefix,
Service: auth.GetSCEPService(),
DB: auth.GetDatabase().(scep.DB),
DNS: dns,
Prefix: scepPrefix,
})
if err != nil {
return nil, errors.Wrap(err, "error creating SCEP authority")
@ -201,8 +200,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
ca.auth = auth
ca.srv = server.New(config.Address, handler, tlsConfig)
// TODO: instead opt for having a single server.Server but two http.Servers
// handling the HTTP vs. HTTPS handler?
// TODO: instead opt for having a single server.Server but two
// http.Servers handling the HTTP and HTTPS handler? The latter
// will probably introduce more complexity in terms of graceful
// reload.
if config.InsecureAddress != "" {
ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil)
}
@ -357,13 +358,14 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) {
return tlsConfig, nil
}
// func dumpRoutes(mux chi.Routes) {
// // helpful routine for logging all routes //
// walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
// fmt.Printf("%s %s\n", method, route)
// return nil
// }
// if err := chi.Walk(mux, walkFunc); err != nil {
// fmt.Printf("Logging err: %s\n", err.Error())
// }
// }
//nolint // ignore linters to allow keeping this function around for debugging
func dumpRoutes(mux chi.Routes) {
// helpful routine for logging all routes //
walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
fmt.Printf("%s %s\n", method, route)
return nil
}
if err := chi.Walk(mux, walkFunc); err != nil {
fmt.Printf("Logging err: %s\n", err.Error())
}
}

View file

@ -24,8 +24,7 @@ type Options struct {
// Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS.
// They are configured in ca.json crt and key properties.
CertificateChain []*x509.Certificate
Signer crypto.Signer `json:"-"`
Decrypter crypto.Decrypter `json:"-"`
Signer crypto.Signer `json:"-"`
// IsCreator is set to true when we're creating a certificate authority. Is
// used to skip some validations when initializing a CertificateAuthority.

View file

@ -16,6 +16,8 @@ type KeyManager interface {
Close() error
}
// Decrypter is an interface implemented by KMSes that are used
// in operations that require decryption
type Decrypter interface {
CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error)
}

View file

@ -138,9 +138,6 @@ type CreateDecrypterRequest struct {
Decrypter crypto.Decrypter
DecryptionKey string
DecryptionKeyPEM []byte
TokenLabel string
PublicKey string
PublicKeyPEM []byte
Password []byte
}

View file

@ -4,8 +4,6 @@ import (
"context"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
@ -18,6 +16,8 @@ import (
"github.com/smallstep/certificates/scep"
"go.mozilla.org/pkcs7"
"github.com/pkg/errors"
microscep "github.com/micromdm/scep/scep"
)
@ -75,7 +75,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
request, err := decodeSCEPRequest(r)
if err != nil {
writeError(w, fmt.Errorf("not a scep get request: %w", err))
writeError(w, errors.Wrap(err, "not a scep get request"))
return
}
@ -90,11 +90,11 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
case opnPKIOperation:
// TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though
default:
err = fmt.Errorf("unknown operation: %s", request.Operation)
err = errors.Errorf("unknown operation: %s", request.Operation)
}
if err != nil {
writeError(w, fmt.Errorf("get request failed: %w", err))
writeError(w, errors.Wrap(err, "get request failed"))
return
}
@ -106,7 +106,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) {
request, err := decodeSCEPRequest(r)
if err != nil {
writeError(w, fmt.Errorf("not a scep post request: %w", err))
writeError(w, errors.Wrap(err, "not a scep post request"))
return
}
@ -117,11 +117,11 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) {
case opnPKIOperation:
response, err = h.PKIOperation(ctx, request)
default:
err = fmt.Errorf("unknown operation: %s", request.Operation)
err = errors.Errorf("unknown operation: %s", request.Operation)
}
if err != nil {
writeError(w, fmt.Errorf("post request failed: %w", err))
writeError(w, errors.Wrap(err, "post request failed"))
return
}
@ -163,7 +163,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) {
Message: decodedMessage,
}, nil
default:
return SCEPRequest{}, fmt.Errorf("unsupported operation: %s", operation)
return SCEPRequest{}, errors.Errorf("unsupported operation: %s", operation)
}
case http.MethodPost:
body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
@ -175,7 +175,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) {
Message: body,
}, nil
default:
return SCEPRequest{}, fmt.Errorf("unsupported method: %s", method)
return SCEPRequest{}, errors.Errorf("unsupported method: %s", method)
}
}
@ -187,7 +187,7 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
name := chi.URLParam(r, "provisionerID")
provisionerID, err := url.PathUnescape(name)
if err != nil {
api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name))
api.WriteError(w, errors.Errorf("error url unescaping provisioner id '%s'", name))
return
}
@ -229,6 +229,9 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) {
if len(certs) == 1 {
response.Data = certs[0].Raw
} else {
// create degenerate pkcs7 certificate structure, according to
// https://tools.ietf.org/html/rfc8894#section-4.2.1.2, because
// not signed or encrypted data has to be returned.
data, err := microscep.DegenerateCertificates(certs)
if err != nil {
return SCEPResponse{}, err
@ -329,12 +332,11 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) {
w.Header().Set("Content-Type", contentHeader(response))
_, err := w.Write(response.Data)
if err != nil {
writeError(w, fmt.Errorf("error when writing scep response: %w", err)) // This could end up as an error again
writeError(w, errors.Wrap(err, "error when writing scep response")) // This could end up as an error again
}
}
func writeError(w http.ResponseWriter, err error) {
// TODO: this probably needs to use SCEP specific errors (i.e. failInfo)
scepError := &scep.Error{
Message: err.Error(),
Status: http.StatusInternalServerError, // TODO: make this a param?

View file

@ -1,32 +1,24 @@
package scep
import (
"bytes"
"context"
"crypto/subtle"
"crypto/x509"
"errors"
"fmt"
"net/url"
"github.com/smallstep/certificates/authority/provisioner"
database "github.com/smallstep/certificates/db"
"github.com/smallstep/nosql"
microx509util "github.com/micromdm/scep/crypto/x509util"
microscep "github.com/micromdm/scep/scep"
//"github.com/smallstep/certificates/scep/pkcs7"
"github.com/pkg/errors"
"go.mozilla.org/pkcs7"
"go.step.sm/crypto/x509util"
)
var (
certTable = []byte("scep_certs")
)
// Interface is the SCEP authority interface.
type Interface interface {
LoadProvisionerByID(string) (provisioner.Interface, error)
@ -42,28 +34,21 @@ type Interface interface {
// Authority is the layer that handles all SCEP interactions.
type Authority struct {
backdate provisioner.Duration
db nosql.DB
prefix string
dns string
// dir *directory
db DB
prefix string
dns string
intermediateCertificate *x509.Certificate
service *Service
signAuth SignAuthority
service *Service
signAuth SignAuthority
}
// AuthorityOptions required to create a new SCEP Authority.
type AuthorityOptions struct {
// Service provides the SCEP functions to Authority
// Service provides the certificate chain, the signer and the decrypter to the Authority
Service *Service
// Backdate
Backdate provisioner.Duration
// DB is the database used by nosql.
DB nosql.DB
// DNS the host used to generate accurate SCEP links. By default the authority
// DB is the database used by SCEP
DB DB
// DNS is the host used to generate accurate SCEP links. By default the authority
// will use the Host from the request, so this value will only be used if
// request.Host is empty.
DNS string
@ -81,19 +66,7 @@ type SignAuthority interface {
// New returns a new Authority that implements the SCEP interface.
func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
if _, ok := ops.DB.(*database.SimpleDB); !ok {
// If it's not a SimpleDB then go ahead and bootstrap the DB with the
// necessary SCEP tables. SimpleDB should ONLY be used for testing.
tables := [][]byte{certTable}
for _, b := range tables {
if err := ops.DB.CreateTable(b); err != nil {
return nil, fmt.Errorf("%w: error creating table %s", err, string(b))
}
}
}
authority := &Authority{
backdate: ops.Backdate,
db: ops.DB,
prefix: ops.Prefix,
dns: ops.DNS,
@ -102,7 +75,7 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) {
// TODO: this is not really nice to do; the Service should be removed
// in its entirety to make this more interoperable with the rest of
// step-ca.
// step-ca, I think.
if ops.Service != nil {
authority.intermediateCertificate = ops.Service.certificateChain[0]
authority.service = ops.Service
@ -200,12 +173,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
p7c, err := pkcs7.Parse(msg.P7.Content)
if err != nil {
return err
return errors.Wrap(err, "error parsing pkcs7 content")
}
envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter)
if err != nil {
return err
return errors.Wrap(err, "error decrypting encrypted pkcs7 content")
}
msg.pkiEnvelope = envelope
@ -214,19 +187,19 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
case microscep.CertRep:
certs, err := microscep.CACerts(msg.pkiEnvelope)
if err != nil {
return err
return errors.Wrap(err, "error extracting CA certs from pkcs7 degenerate data")
}
msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this
msg.CertRepMessage.Certificate = certs[0]
return nil
case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq:
csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope)
if err != nil {
return fmt.Errorf("parse CSR from pkiEnvelope: %w", err)
return errors.Wrap(err, "parse CSR from pkiEnvelope")
}
// check for challengePassword
cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope)
if err != nil {
return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err)
return errors.Wrap(err, "parse challenge password in pkiEnvelope")
}
msg.CSRReqMessage = &microscep.CSRReqMessage{
RawDecrypted: msg.pkiEnvelope,
@ -235,7 +208,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
}
return nil
case microscep.GetCRL, microscep.GetCert, microscep.CertPoll:
return fmt.Errorf("not implemented") //errNotImplemented
return errors.Errorf("not implemented")
}
return nil
@ -266,16 +239,13 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
}
// Template data
data := x509util.NewTemplateData()
data.SetCommonName(csr.Subject.CommonName)
data.SetSANs(csr.DNSNames)
data.SetCertificateRequest(csr)
data := x509util.CreateTemplateData(csr.Subject.CommonName, csr.DNSNames)
// Get authorizations from the SCEP provisioner.
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOps, err := p.AuthorizeSign(ctx, "")
if err != nil {
return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err)
return nil, errors.Wrap(err, "error retrieving authorization options from SCEP provisioner")
}
opts := provisioner.SignOptions{
@ -285,19 +255,20 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
if err != nil {
return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err)
return nil, errors.Wrap(err, "error creating template options from SCEP provisioner")
}
signOps = append(signOps, templateOptions)
certChain, err := a.signAuth.Sign(csr, opts, signOps...)
if err != nil {
return nil, fmt.Errorf("error generating certificate for order %w", err)
return nil, errors.Wrap(err, "error generating certificate for order")
}
// take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2
cert := certChain[0]
// create a degenerate cert structure
deg, err := degenerateCertificates([]*x509.Certificate{cert})
// and create a degenerate cert structure
deg, err := microscep.DegenerateCertificates([]*x509.Certificate{cert})
if err != nil {
return nil, err
}
@ -370,13 +341,12 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
CertRepMessage: cr,
}
// TODO: save more data?
_, err = newCert(a.db, CertOptions{
// store the newly created certificate
err = newCert(a.db, CertOptions{
Leaf: certChain[0],
Intermediates: certChain[1:],
})
if err != nil {
fmt.Println(err)
return nil, err
}
@ -459,7 +429,7 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string)
return false, err
}
if p.GetChallengePassword() == password {
if subtle.ConstantTimeCompare([]byte(p.GetChallengePassword()), []byte(password)) == 1 {
return true, nil
}
@ -491,21 +461,3 @@ func (a *Authority) GetCACaps(ctx context.Context) []string {
return caps
}
// degenerateCertificates creates degenerate certificates pkcs#7 type
func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) {
var buf bytes.Buffer
for _, cert := range certs {
buf.Write(cert.Raw)
}
degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes())
if err != nil {
return nil, err
}
return degenerate, nil
}
// Interface guards
var (
_ Interface = (*Authority)(nil)
)

View file

@ -2,79 +2,20 @@ package scep
import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"time"
"github.com/smallstep/nosql"
"github.com/pkg/errors"
)
type certificate struct {
ID string `json:"id"`
Created time.Time `json:"created"`
Leaf []byte `json:"leaf"`
Intermediates []byte `json:"intermediates"`
}
// CertOptions options with which to create and store a cert object.
type CertOptions struct {
Leaf *x509.Certificate
Intermediates []*x509.Certificate
}
func newCert(db nosql.DB, ops CertOptions) (*certificate, error) {
// TODO: according to the RFC this should be IssuerAndSerialNumber,
// but sscep seems to use just the serial number for getcert
id := ops.Leaf.SerialNumber.String()
leaf := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: ops.Leaf.Raw,
})
var intermediates []byte
for _, cert := range ops.Intermediates {
intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})...)
}
cert := &certificate{
ID: id,
Leaf: leaf,
Intermediates: intermediates,
Created: time.Now().UTC(),
}
certB, err := json.Marshal(cert)
func newCert(db DB, ops CertOptions) error {
err := db.StoreCertificate(ops.Leaf)
if err != nil {
return nil, fmt.Errorf("%w: error marshaling certificate", err)
}
_, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB)
switch {
case err != nil:
return nil, fmt.Errorf("%w: error storing certificate", err)
case !swapped:
return nil, fmt.Errorf("error storing certificate; " +
"value has changed since last read")
default:
return cert, nil
errors.Wrap(err, "error while storing certificate")
}
return nil
}
// func getCert(db nosql.DB, id string) (*certificate, error) {
// b, err := db.Get(certTable, []byte(id))
// if nosql.IsErrNotFound(err) {
// return nil, fmt.Errorf("certificate %s not found", id)
// } else if err != nil {
// return nil, fmt.Errorf("error loading certificate")
// }
// var cert certificate
// if err := json.Unmarshal(b, &cert); err != nil {
// return nil, fmt.Errorf("%w: error unmarshaling certificate", err)
// }
// return &cert, nil
// }

7
scep/database.go Normal file
View file

@ -0,0 +1,7 @@
package scep
import "crypto/x509"
type DB interface {
StoreCertificate(crt *x509.Certificate) error
}

31
scep/options.go Normal file
View file

@ -0,0 +1,31 @@
package scep
import (
"crypto"
"crypto/x509"
)
type Options struct {
// CertificateChain is the issuer certificate, along with any other bundled certificates
// to be returned in the chain for consumers. Configured in ca.json crt property.
CertificateChain []*x509.Certificate
// Signer signs CSRs in SCEP. Configured in ca.json key property.
Signer crypto.Signer `json:"-"`
// Decrypter decrypts encrypted SCEP messages. Configured in ca.json key property.
Decrypter crypto.Decrypter `json:"-"`
}
// Validate checks the fields in Options.
func (o *Options) Validate() error {
// var typ Type
// if o == nil {
// typ = Type(SoftCAS)
// } else {
// typ = Type(o.Type)
// }
// // Check that the type can be loaded.
// if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok {
// return errors.Errorf("unsupported cas type %s", typ)
// }
return nil
}

View file

@ -4,9 +4,6 @@ import (
"context"
"crypto"
"crypto/x509"
"strings"
"github.com/smallstep/certificates/cas/apiv1"
)
// Service is a wrapper for crypto.Signer and crypto.Decrypter
@ -16,20 +13,12 @@ type Service struct {
decrypter crypto.Decrypter
}
func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) {
func NewService(ctx context.Context, opts Options) (*Service, error) {
if err := opts.Validate(); err != nil {
return nil, err
}
t := apiv1.Type(strings.ToLower(opts.Type))
if t == apiv1.DefaultCAS {
t = apiv1.SoftCAS
}
// TODO: silence the linter (temporarily)
_ = t
// TODO: should this become similar to the New CertificateAuthorityService as in x509CAService?
return &Service{
certificateChain: opts.CertificateChain,