From 4fe7179b95682f1fdd157fc92a274c09567901b0 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 7 Mar 2021 00:50:00 +0100 Subject: [PATCH] Add support for configuring capabilities (cacaps) --- authority/provisioner/scep.go | 6 +++++ scep/api/api.go | 22 ++++-------------- scep/authority.go | 43 +++++++++++++++++++++++++++++++++-- scep/provisioner.go | 1 + 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 031241c4..49d057ff 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -16,6 +16,7 @@ type SCEP struct { ForceCN bool `json:"forceCN,omitempty"` ChallengePassword string `json:"challenge,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer @@ -97,6 +98,11 @@ func (s *SCEP) GetChallengePassword() string { return s.ChallengePassword } +// GetCapabilities returns the CA capabilities +func (s *SCEP) GetCapabilities() []string { + return s.Capabilities +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/api/api.go b/scep/api/api.go index 3f49f7a3..f6294f06 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -33,20 +33,6 @@ const maxPayloadSize = 2 << 20 type nextHTTP = func(http.ResponseWriter, *http.Request) -var ( - // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 - // TODO: move capabilities to Authority or Provisioner, so that they can be configured? - defaultCapabilities = []string{ - "Renewal", - "SHA-1", - "SHA-256", - "AES", - "DES3", - "SCEPStandard", - "POSTPKIOperation", - } -) - const ( certChainHeader = "application/x-x509-ca-ra-cert" leafHeader = "application/x-x509-ca-cert" @@ -260,10 +246,12 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { // GetCACaps returns the CA capabilities in a SCEP response func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { - response := SCEPResponse{Operation: opnGetCACaps} + caps := h.Auth.GetCACaps(ctx) - // TODO: get the actual capabilities from provisioner config - response.Data = formatCapabilities(defaultCapabilities) + response := SCEPResponse{ + Operation: opnGetCACaps, + Data: formatCapabilities(caps), + } return response, nil } diff --git a/scep/authority.go b/scep/authority.go index a2b3b8b9..f8e5a76f 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -57,6 +57,7 @@ type Interface interface { DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) MatchChallengePassword(ctx context.Context, password string) (bool, error) + GetCACaps(ctx context.Context) []string GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } @@ -128,6 +129,19 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { }, nil } +var ( + // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 + defaultCapabilities = []string{ + "Renewal", + "SHA-1", + "SHA-256", + "AES", + "DES3", + "SCEPStandard", + "POSTPKIOperation", + } +) + // LoadProvisionerByID calls out to the SignAuthority interface to load a // provisioner by ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { @@ -155,9 +169,9 @@ func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *u u = *baseURL } - // If no Scheme is set, then default to https. + // If no Scheme is set, then default to http (in case of SCEP) if u.Scheme == "" { - u.Scheme = "https" + u.Scheme = "http" } // If no Host is set, then use the default (first DNS attr in the ca.json). @@ -188,6 +202,9 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { // // Using an RA does not seem to exist in https://tools.ietf.org/html/rfc8894, but is mentioned in // https://tools.ietf.org/id/draft-nourse-scep-21.html. Will continue using the CA directly for now. + // + // The certificate to use should probably depend on the (configured) Provisioner and may + // use a distinct certificate, apart from the intermediate. if a.intermediateCertificate == nil { return nil, errors.New("no intermediate certificate available in SCEP authority") @@ -421,6 +438,28 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string) return false, nil } +// GetCACaps returns the CA capabilities +func (a *Authority) GetCACaps(ctx context.Context) []string { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return defaultCapabilities + } + + caps := p.GetCapabilities() + if len(caps) == 0 { + return defaultCapabilities + } + + // TODO: validate the caps? Ensure they are the right format according to RFC? + // TODO: ensure that the capabilities are actually "enforced"/"verified" in code too: + // check that only parts of the spec are used in the implementation belonging to the capabilities. + // For example for renewals, which we could disable in the provisioner, should then also + // not be reported in cacaps operation. + + return caps +} + // degenerateCertificates creates degenerate certificates pkcs#7 type func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer diff --git a/scep/provisioner.go b/scep/provisioner.go index 5c665a1f..e1b7f8b1 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -15,4 +15,5 @@ type Provisioner interface { DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options GetChallengePassword() string + GetCapabilities() []string }