Merge branch 'master' into herman/allow-deny

This commit is contained in:
Herman Slatman 2022-03-24 18:35:20 +01:00
commit 23676d3bcc
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
7 changed files with 163 additions and 170 deletions

View file

@ -13,7 +13,6 @@ import (
"github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/scep"
) )
// WriteError writes to w a JSON representation of the given error. // WriteError writes to w a JSON representation of the given error.
@ -25,22 +24,9 @@ func WriteError(w http.ResponseWriter, err error) {
case *admin.Error: case *admin.Error:
admin.WriteError(w, k) admin.WriteError(w, k)
return return
case *scep.Error:
w.Header().Set("Content-Type", "text/plain")
default:
w.Header().Set("Content-Type", "application/json")
} }
cause := errors.Cause(err) cause := errors.Cause(err)
if sc, ok := err.(errs.StatusCoder); ok {
w.WriteHeader(sc.StatusCode())
} else {
if sc, ok := cause.(errs.StatusCoder); ok {
w.WriteHeader(sc.StatusCode())
} else {
w.WriteHeader(http.StatusInternalServerError)
}
}
// Write errors in the response writer // Write errors in the response writer
if rl, ok := w.(logging.ResponseLogger); ok { if rl, ok := w.(logging.ResponseLogger); ok {
@ -60,6 +46,16 @@ func WriteError(w http.ResponseWriter, err error) {
} }
} }
code := http.StatusInternalServerError
if sc, ok := err.(errs.StatusCoder); ok {
code = sc.StatusCode()
} else if sc, ok := cause.(errs.StatusCoder); ok {
code = sc.StatusCode()
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(err); err != nil { if err := json.NewEncoder(w).Encode(err); err != nil {
log.Error(w, err) log.Error(w, err)
} }

View file

@ -117,7 +117,7 @@ func main() {
app.HelpName = "step-ca" app.HelpName = "step-ca"
app.Version = step.Version() app.Version = step.Version()
app.Usage = "an online certificate authority for secure automated certificate management" app.Usage = "an online certificate authority for secure automated certificate management"
app.UsageText = `**step-ca** <config> [**--password-file**=<file>] app.UsageText = `**step-ca** [config] [**--context**=<name>] [**--password-file**=<file>]
[**--ssh-host-password-file**=<file>] [**--ssh-user-password-file**=<file>] [**--ssh-host-password-file**=<file>] [**--ssh-user-password-file**=<file>]
[**--issuer-password-file**=<file>] [**--resolver**=<addr>] [**--help**] [**--version**]` [**--issuer-password-file**=<file>] [**--resolver**=<addr>] [**--help**] [**--version**]`
app.Description = `**step-ca** runs the Step Online Certificate Authority app.Description = `**step-ca** runs the Step Online Certificate Authority
@ -133,6 +133,7 @@ This command will run indefinitely on success and return \>0 if any error occurs
These examples assume that you have already initialized your PKI by running These examples assume that you have already initialized your PKI by running
'step ca init'. If you have not completed this step please see the 'Getting Started' 'step ca init'. If you have not completed this step please see the 'Getting Started'
section of the README. section of the README.
Run the Step CA and prompt for password: Run the Step CA and prompt for password:
''' '''
$ step-ca $STEPPATH/config/ca.json $ step-ca $STEPPATH/config/ca.json
@ -141,7 +142,26 @@ Run the Step CA and read the password from a file - this is useful for
automating deployment: automating deployment:
''' '''
$ step-ca $STEPPATH/config/ca.json --password-file ./password.txt $ step-ca $STEPPATH/config/ca.json --password-file ./password.txt
'''` '''
Run the Step CA for the context selected with step and a custom password file:
'''
$ step context select ssh
$ step-ca --password-file ./password.txt
'''
Run the Step CA for the context named _mybiz_ and prompt for password:
'''
$ step-ca --context=mybiz
'''
Run the Step CA for the context named _mybiz_ and an alternate ca.json file:
'''
$ step-ca --context=mybiz other-ca.json
'''
Run the Step CA for the context named _mybiz_ and read the password from a file - this is useful for
automating deployment:
'''
$ step-ca --context=mybiz --password-file ./password.txt
'''
`
app.Flags = append(app.Flags, commands.AppCommand.Flags...) app.Flags = append(app.Flags, commands.AppCommand.Flags...)
app.Flags = append(app.Flags, cli.HelpFlag) app.Flags = append(app.Flags, cli.HelpFlag)
app.Copyright = fmt.Sprintf("(c) 2018-%d Smallstep Labs, Inc.", time.Now().Year()) app.Copyright = fmt.Sprintf("(c) 2018-%d Smallstep Labs, Inc.", time.Now().Year())

View file

@ -16,6 +16,7 @@ import (
"github.com/smallstep/certificates/pki" "github.com/smallstep/certificates/pki"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/cli-utils/errs" "go.step.sm/cli-utils/errs"
"go.step.sm/cli-utils/step"
) )
// AppCommand is the action used as the top action. // AppCommand is the action used as the top action.
@ -57,6 +58,11 @@ certificate issuer private key used in the RA mode.`,
Usage: "token used to enable the linked ca.", Usage: "token used to enable the linked ca.",
EnvVar: "STEP_CA_TOKEN", EnvVar: "STEP_CA_TOKEN",
}, },
cli.StringFlag{
Name: "context",
Usage: "The name of the authority's context.",
EnvVar: "STEP_CA_CONTEXT",
},
}, },
} }
@ -69,15 +75,23 @@ func appAction(ctx *cli.Context) error {
resolver := ctx.String("resolver") resolver := ctx.String("resolver")
token := ctx.String("token") token := ctx.String("token")
// If zero cmd line args show help, if >1 cmd line args show error. if ctx.NArg() > 1 {
if ctx.NArg() == 0 { return errs.TooManyArguments(ctx)
return cli.ShowAppHelp(ctx) }
}
if err := errs.NumberOfArguments(ctx, 1); err != nil { if caCtx := ctx.String("context"); caCtx != "" {
return err if err := step.Contexts().SetCurrent(caCtx); err != nil {
return err
}
}
var configFile string
if ctx.NArg() > 0 {
configFile = ctx.Args().Get(0)
} else {
configFile = step.CaConfigFile()
} }
configFile := ctx.Args().Get(0)
cfg, err := config.LoadConfiguration(configFile) cfg, err := config.LoadConfiguration(configFile)
if err != nil { if err != nil {
fatal(err) fatal(err)

View file

@ -1,9 +1,12 @@
// Package api implements a SCEP HTTP server.
package api package api
import ( import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -11,7 +14,6 @@ import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
microscep "github.com/micromdm/scep/v2/scep" microscep "github.com/micromdm/scep/v2/scep"
"github.com/pkg/errors"
"go.mozilla.org/pkcs7" "go.mozilla.org/pkcs7"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
@ -30,22 +32,14 @@ const (
const maxPayloadSize = 2 << 20 const maxPayloadSize = 2 << 20
type nextHTTP = func(http.ResponseWriter, *http.Request) // request is a SCEP server request.
type request struct {
const (
certChainHeader = "application/x-x509-ca-ra-cert"
leafHeader = "application/x-x509-ca-cert"
pkiOperationHeader = "application/x-pki-message"
)
// SCEPRequest is a SCEP server request.
type SCEPRequest struct {
Operation string Operation string
Message []byte Message []byte
} }
// SCEPResponse is a SCEP server response. // response is a SCEP server response.
type SCEPResponse struct { type response struct {
Operation string Operation string
CACertNum int CACertNum int
Data []byte Data []byte
@ -53,19 +47,21 @@ type SCEPResponse struct {
Error error Error error
} }
// Handler is the SCEP request handler. // handler is the SCEP request handler.
type Handler struct { type handler struct {
Auth scep.Interface auth *scep.Authority
} }
// New returns a new SCEP API router. // New returns a new SCEP API router.
func New(scepAuth scep.Interface) api.RouterHandler { func New(auth *scep.Authority) api.RouterHandler {
return &Handler{scepAuth} return &handler{
auth: auth,
}
} }
// Route traffic and implement the Router interface. // Route traffic and implement the Router interface.
func (h *Handler) Route(r api.Router) { func (h *handler) Route(r api.Router) {
getLink := h.Auth.GetLinkExplicit getLink := h.auth.GetLinkExplicit
r.MethodFunc(http.MethodGet, getLink("{provisionerName}/*", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodGet, getLink("{provisionerName}/*", false, nil), h.lookupProvisioner(h.Get))
r.MethodFunc(http.MethodGet, getLink("{provisionerName}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodGet, getLink("{provisionerName}", false, nil), h.lookupProvisioner(h.Get))
r.MethodFunc(http.MethodPost, getLink("{provisionerName}/*", false, nil), h.lookupProvisioner(h.Post)) r.MethodFunc(http.MethodPost, getLink("{provisionerName}/*", false, nil), h.lookupProvisioner(h.Post))
@ -73,64 +69,64 @@ func (h *Handler) Route(r api.Router) {
} }
// Get handles all SCEP GET requests // Get handles all SCEP GET requests
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { func (h *handler) Get(w http.ResponseWriter, r *http.Request) {
request, err := decodeSCEPRequest(r) req, err := decodeRequest(r)
if err != nil { if err != nil {
writeError(w, errors.Wrap(err, "invalid scep get request")) fail(w, fmt.Errorf("invalid scep get request: %w", err))
return return
} }
ctx := r.Context() ctx := r.Context()
var response SCEPResponse var res response
switch request.Operation { switch req.Operation {
case opnGetCACert: case opnGetCACert:
response, err = h.GetCACert(ctx) res, err = h.GetCACert(ctx)
case opnGetCACaps: case opnGetCACaps:
response, err = h.GetCACaps(ctx) res, err = h.GetCACaps(ctx)
case opnPKIOperation: case opnPKIOperation:
// TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though // TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though
default: default:
err = errors.Errorf("unknown operation: %s", request.Operation) err = fmt.Errorf("unknown operation: %s", req.Operation)
} }
if err != nil { if err != nil {
writeError(w, errors.Wrap(err, "scep get request failed")) fail(w, fmt.Errorf("scep get request failed: %w", err))
return return
} }
writeSCEPResponse(w, response) writeResponse(w, res)
} }
// Post handles all SCEP POST requests // Post handles all SCEP POST requests
func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { func (h *handler) Post(w http.ResponseWriter, r *http.Request) {
request, err := decodeSCEPRequest(r) req, err := decodeRequest(r)
if err != nil { if err != nil {
writeError(w, errors.Wrap(err, "invalid scep post request")) fail(w, fmt.Errorf("invalid scep post request: %w", err))
return return
} }
ctx := r.Context() ctx := r.Context()
var response SCEPResponse var res response
switch request.Operation { switch req.Operation {
case opnPKIOperation: case opnPKIOperation:
response, err = h.PKIOperation(ctx, request) res, err = h.PKIOperation(ctx, req)
default: default:
err = errors.Errorf("unknown operation: %s", request.Operation) err = fmt.Errorf("unknown operation: %s", req.Operation)
} }
if err != nil { if err != nil {
writeError(w, errors.Wrap(err, "scep post request failed")) fail(w, fmt.Errorf("scep post request failed: %w", err))
return return
} }
writeSCEPResponse(w, response) writeResponse(w, res)
} }
func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { func decodeRequest(r *http.Request) (request, error) {
defer r.Body.Close() defer r.Body.Close()
@ -146,7 +142,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) {
case http.MethodGet: case http.MethodGet:
switch operation { switch operation {
case opnGetCACert, opnGetCACaps: case opnGetCACert, opnGetCACaps:
return SCEPRequest{ return request{
Operation: operation, Operation: operation,
Message: []byte{}, Message: []byte{},
}, nil }, nil
@ -158,50 +154,50 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) {
// TODO: verify this; it seems like it should be StdEncoding instead of URLEncoding // TODO: verify this; it seems like it should be StdEncoding instead of URLEncoding
decodedMessage, err := base64.URLEncoding.DecodeString(message) decodedMessage, err := base64.URLEncoding.DecodeString(message)
if err != nil { if err != nil {
return SCEPRequest{}, err return request{}, err
} }
return SCEPRequest{ return request{
Operation: operation, Operation: operation,
Message: decodedMessage, Message: decodedMessage,
}, nil }, nil
default: default:
return SCEPRequest{}, errors.Errorf("unsupported operation: %s", operation) return request{}, fmt.Errorf("unsupported operation: %s", operation)
} }
case http.MethodPost: case http.MethodPost:
body, err := io.ReadAll(io.LimitReader(r.Body, maxPayloadSize)) body, err := io.ReadAll(io.LimitReader(r.Body, maxPayloadSize))
if err != nil { if err != nil {
return SCEPRequest{}, err return request{}, err
} }
return SCEPRequest{ return request{
Operation: operation, Operation: operation,
Message: body, Message: body,
}, nil }, nil
default: default:
return SCEPRequest{}, errors.Errorf("unsupported method: %s", method) return request{}, fmt.Errorf("unsupported method: %s", method)
} }
} }
// lookupProvisioner loads the provisioner associated with the request. // lookupProvisioner loads the provisioner associated with the request.
// Responds 404 if the provisioner does not exist. // Responds 404 if the provisioner does not exist.
func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { func (h *handler) lookupProvisioner(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "provisionerName") name := chi.URLParam(r, "provisionerName")
provisionerName, err := url.PathUnescape(name) provisionerName, err := url.PathUnescape(name)
if err != nil { if err != nil {
api.WriteError(w, errors.Errorf("error url unescaping provisioner name '%s'", name)) fail(w, fmt.Errorf("error url unescaping provisioner name '%s'", name))
return return
} }
p, err := h.Auth.LoadProvisionerByName(provisionerName) p, err := h.auth.LoadProvisionerByName(provisionerName)
if err != nil { if err != nil {
api.WriteError(w, err) fail(w, err)
return return
} }
prov, ok := p.(*provisioner.SCEP) prov, ok := p.(*provisioner.SCEP)
if !ok { if !ok {
api.WriteError(w, errors.New("provisioner must be of type SCEP")) fail(w, errors.New("provisioner must be of type SCEP"))
return return
} }
@ -212,59 +208,59 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
} }
// GetCACert returns the CA certificates in a SCEP response // GetCACert returns the CA certificates in a SCEP response
func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { func (h *handler) GetCACert(ctx context.Context) (response, error) {
certs, err := h.Auth.GetCACertificates(ctx) certs, err := h.auth.GetCACertificates(ctx)
if err != nil { if err != nil {
return SCEPResponse{}, err return response{}, err
} }
if len(certs) == 0 { if len(certs) == 0 {
return SCEPResponse{}, errors.New("missing CA cert") return response{}, errors.New("missing CA cert")
} }
response := SCEPResponse{ res := response{
Operation: opnGetCACert, Operation: opnGetCACert,
CACertNum: len(certs), CACertNum: len(certs),
} }
if len(certs) == 1 { if len(certs) == 1 {
response.Data = certs[0].Raw res.Data = certs[0].Raw
} else { } else {
// create degenerate pkcs7 certificate structure, according to // create degenerate pkcs7 certificate structure, according to
// https://tools.ietf.org/html/rfc8894#section-4.2.1.2, because // https://tools.ietf.org/html/rfc8894#section-4.2.1.2, because
// not signed or encrypted data has to be returned. // not signed or encrypted data has to be returned.
data, err := microscep.DegenerateCertificates(certs) data, err := microscep.DegenerateCertificates(certs)
if err != nil { if err != nil {
return SCEPResponse{}, err return response{}, err
} }
response.Data = data res.Data = data
} }
return response, nil return res, nil
} }
// GetCACaps returns the CA capabilities in a SCEP response // GetCACaps returns the CA capabilities in a SCEP response
func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { func (h *handler) GetCACaps(ctx context.Context) (response, error) {
caps := h.Auth.GetCACaps(ctx) caps := h.auth.GetCACaps(ctx)
response := SCEPResponse{ res := response{
Operation: opnGetCACaps, Operation: opnGetCACaps,
Data: formatCapabilities(caps), Data: formatCapabilities(caps),
} }
return response, nil return res, nil
} }
// PKIOperation performs PKI operations and returns a SCEP response // PKIOperation performs PKI operations and returns a SCEP response
func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { func (h *handler) PKIOperation(ctx context.Context, req request) (response, error) {
// parse the message using microscep implementation // parse the message using microscep implementation
microMsg, err := microscep.ParsePKIMessage(request.Message) microMsg, err := microscep.ParsePKIMessage(req.Message)
if err != nil { if err != nil {
// return the error, because we can't use the msg for creating a CertRep // return the error, because we can't use the msg for creating a CertRep
return SCEPResponse{}, err return response{}, err
} }
// this is essentially doing the same as microscep.ParsePKIMessage, but // this is essentially doing the same as microscep.ParsePKIMessage, but
@ -272,7 +268,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe
// wrapper for the microscep implementation. // wrapper for the microscep implementation.
p7, err := pkcs7.Parse(microMsg.Raw) p7, err := pkcs7.Parse(microMsg.Raw)
if err != nil { if err != nil {
return SCEPResponse{}, err return response{}, err
} }
// copy over properties to our internal PKIMessage // copy over properties to our internal PKIMessage
@ -284,8 +280,8 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe
P7: p7, P7: p7,
} }
if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { if err := h.auth.DecryptPKIEnvelope(ctx, msg); err != nil {
return SCEPResponse{}, err return response{}, err
} }
// NOTE: at this point we have sufficient information for returning nicely signed CertReps // NOTE: at this point we have sufficient information for returning nicely signed CertReps
@ -297,7 +293,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe
// a certificate exists; then it will use RenewalReq. Adding the challenge check here may be a small breaking change for clients. // a certificate exists; then it will use RenewalReq. Adding the challenge check here may be a small breaking change for clients.
// We'll have to see how it works out. // We'll have to see how it works out.
if msg.MessageType == microscep.PKCSReq || msg.MessageType == microscep.RenewalReq { if msg.MessageType == microscep.PKCSReq || msg.MessageType == microscep.RenewalReq {
challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) challengeMatches, err := h.auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword)
if err != nil { if err != nil {
return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("error when checking password")) return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("error when checking password"))
} }
@ -315,72 +311,67 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe
// Authentication by the (self-signed) certificate with an optional challenge is required; supporting renewals incl. verification // Authentication by the (self-signed) certificate with an optional challenge is required; supporting renewals incl. verification
// of the client cert is not. // of the client cert is not.
certRep, err := h.Auth.SignCSR(ctx, csr, msg) certRep, err := h.auth.SignCSR(ctx, csr, msg)
if err != nil { if err != nil {
return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.Wrap(err, "error when signing new certificate")) return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, fmt.Errorf("error when signing new certificate: %w", err))
} }
response := SCEPResponse{ res := response{
Operation: opnPKIOperation, Operation: opnPKIOperation,
Data: certRep.Raw, Data: certRep.Raw,
Certificate: certRep.Certificate, Certificate: certRep.Certificate,
} }
return response, nil return res, nil
} }
func formatCapabilities(caps []string) []byte { func formatCapabilities(caps []string) []byte {
return []byte(strings.Join(caps, "\r\n")) return []byte(strings.Join(caps, "\r\n"))
} }
// writeSCEPResponse writes a SCEP response back to the SCEP client. // writeResponse writes a SCEP response back to the SCEP client.
func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { func writeResponse(w http.ResponseWriter, res response) {
if response.Error != nil { if res.Error != nil {
log.Error(w, response.Error) log.Error(w, res.Error)
} }
if response.Certificate != nil { if res.Certificate != nil {
api.LogCertificate(w, response.Certificate) api.LogCertificate(w, res.Certificate)
} }
w.Header().Set("Content-Type", contentHeader(response)) w.Header().Set("Content-Type", contentHeader(res))
_, err := w.Write(response.Data) _, _ = w.Write(res.Data)
if err != nil {
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) { func fail(w http.ResponseWriter, err error) {
scepError := &scep.Error{ log.Error(w, err)
Message: err.Error(),
Status: http.StatusInternalServerError, // TODO: make this a param? http.Error(w, err.Error(), http.StatusInternalServerError)
}
api.WriteError(w, scepError)
} }
func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, failError error) (SCEPResponse, error) { func (h *handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, failError error) (response, error) {
certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), failError.Error()) certRepMsg, err := h.auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), failError.Error())
if err != nil { if err != nil {
return SCEPResponse{}, err return response{}, err
} }
return SCEPResponse{ return response{
Operation: opnPKIOperation, Operation: opnPKIOperation,
Data: certRepMsg.Raw, Data: certRepMsg.Raw,
Error: failError, Error: failError,
}, nil }, nil
} }
func contentHeader(r SCEPResponse) string { func contentHeader(r response) string {
switch r.Operation { switch r.Operation {
case opnGetCACert:
if r.CACertNum > 1 {
return certChainHeader
}
return leafHeader
case opnPKIOperation:
return pkiOperationHeader
default: default:
return "text/plain" return "text/plain"
case opnGetCACert:
if r.CACertNum > 1 {
return "application/x-x509-ca-ra-cert"
}
return "application/x-x509-ca-cert"
case opnPKIOperation:
return "application/x-pki-message"
} }
} }

View file

@ -4,33 +4,19 @@ import (
"context" "context"
"crypto/subtle" "crypto/subtle"
"crypto/x509" "crypto/x509"
"errors"
"fmt"
"net/url" "net/url"
"github.com/smallstep/certificates/authority/provisioner"
microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util" microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util"
microscep "github.com/micromdm/scep/v2/scep" microscep "github.com/micromdm/scep/v2/scep"
"github.com/pkg/errors"
"go.mozilla.org/pkcs7" "go.mozilla.org/pkcs7"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/authority/provisioner"
) )
// Interface is the SCEP authority interface.
type Interface interface {
LoadProvisionerByName(string) (provisioner.Interface, error)
GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
GetCACertificates(ctx context.Context) ([]*x509.Certificate, error)
DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error
SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error)
CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error)
MatchChallengePassword(ctx context.Context, password string) (bool, error)
GetCACaps(ctx context.Context) []string
}
// Authority is the layer that handles all SCEP interactions. // Authority is the layer that handles all SCEP interactions.
type Authority struct { type Authority struct {
prefix string prefix string
@ -180,12 +166,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
p7c, err := pkcs7.Parse(msg.P7.Content) p7c, err := pkcs7.Parse(msg.P7.Content)
if err != nil { if err != nil {
return errors.Wrap(err, "error parsing pkcs7 content") return fmt.Errorf("error parsing pkcs7 content: %w", err)
} }
envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter) envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter)
if err != nil { if err != nil {
return errors.Wrap(err, "error decrypting encrypted pkcs7 content") return fmt.Errorf("error decrypting encrypted pkcs7 content: %w", err)
} }
msg.pkiEnvelope = envelope msg.pkiEnvelope = envelope
@ -194,19 +180,19 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
case microscep.CertRep: case microscep.CertRep:
certs, err := microscep.CACerts(msg.pkiEnvelope) certs, err := microscep.CACerts(msg.pkiEnvelope)
if err != nil { if err != nil {
return errors.Wrap(err, "error extracting CA certs from pkcs7 degenerate data") return fmt.Errorf("error extracting CA certs from pkcs7 degenerate data: %w", err)
} }
msg.CertRepMessage.Certificate = certs[0] msg.CertRepMessage.Certificate = certs[0]
return nil return nil
case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq:
csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope)
if err != nil { if err != nil {
return errors.Wrap(err, "parse CSR from pkiEnvelope") return fmt.Errorf("parse CSR from pkiEnvelope: %w", err)
} }
// check for challengePassword // check for challengePassword
cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope)
if err != nil { if err != nil {
return errors.Wrap(err, "parse challenge password in pkiEnvelope") return fmt.Errorf("parse challenge password in pkiEnvelope: %w", err)
} }
msg.CSRReqMessage = &microscep.CSRReqMessage{ msg.CSRReqMessage = &microscep.CSRReqMessage{
RawDecrypted: msg.pkiEnvelope, RawDecrypted: msg.pkiEnvelope,
@ -215,7 +201,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err
} }
return nil return nil
case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: case microscep.GetCRL, microscep.GetCert, microscep.CertPoll:
return errors.Errorf("not implemented") return errors.New("not implemented")
} }
return nil return nil
@ -274,19 +260,19 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m
ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod)
signOps, err := p.AuthorizeSign(ctx, "") signOps, err := p.AuthorizeSign(ctx, "")
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error retrieving authorization options from SCEP provisioner") return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err)
} }
opts := provisioner.SignOptions{} opts := provisioner.SignOptions{}
templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating template options from SCEP provisioner") return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err)
} }
signOps = append(signOps, templateOptions) signOps = append(signOps, templateOptions)
certChain, err := a.signAuth.Sign(csr, opts, signOps...) certChain, err := a.signAuth.Sign(csr, opts, signOps...)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error generating certificate for order") return nil, fmt.Errorf("error generating certificate for order: %w", err)
} }
// take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2 // take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2

View file

@ -1,12 +0,0 @@
package scep
// Error is an SCEP error type
type Error struct {
Message string `json:"message"`
Status int `json:"-"`
}
// Error implements the error interface.
func (e *Error) Error() string {
return e.Message
}

View file

@ -1,3 +1,4 @@
// Package scep implements Simple Certificate Enrollment Protocol related functionality.
package scep package scep
import ( import (
@ -5,9 +6,6 @@ import (
"encoding/asn1" "encoding/asn1"
microscep "github.com/micromdm/scep/v2/scep" microscep "github.com/micromdm/scep/v2/scep"
//"github.com/smallstep/certificates/scep/pkcs7"
"go.mozilla.org/pkcs7" "go.mozilla.org/pkcs7"
) )