Merge branch 'master' into herman/allow-deny
This commit is contained in:
commit
23676d3bcc
7 changed files with 163 additions and 170 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)
|
||||||
|
|
201
scep/api/api.go
201
scep/api/api.go
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = µscep.CSRReqMessage{
|
msg.CSRReqMessage = µscep.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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue