Skip to content

Commit

Permalink
Add rudimentary (and incomplete) support for SCEP
Browse files Browse the repository at this point in the history
  • Loading branch information
hslatman authored and dopey committed May 26, 2021
1 parent ff7b829 commit 48c8671
Show file tree
Hide file tree
Showing 10 changed files with 698 additions and 12 deletions.
10 changes: 7 additions & 3 deletions authority/authority.go
Expand Up @@ -442,9 +442,13 @@ func (a *Authority) init() error {
audiences := a.config.GetAudiences()
a.provisioners = provisioner.NewCollection(audiences)
config := provisioner.Config{
Claims: claimer.Claims(),
Audiences: audiences,
DB: a.db,
// TODO: this probably shouldn't happen like this; via SignAuth instead?
IntermediateCert: a.config.IntermediateCert,
SigningKey: a.config.IntermediateKey,
CACertificates: a.rootX509Certs,
Claims: claimer.Claims(),
Audiences: audiences,
DB: a.db,
SSHKeys: &provisioner.SSHKeys{
UserKeys: sshKeys.UserKeys,
HostKeys: sshKeys.HostKeys,
Expand Down
10 changes: 10 additions & 0 deletions authority/provisioner/provisioner.go
Expand Up @@ -142,6 +142,8 @@ const (
TypeK8sSA Type = 8
// TypeSSHPOP is used to indicate the SSHPOP provisioners.
TypeSSHPOP Type = 9
// TypeSCEP is used to indicate the SCEP provisioners
TypeSCEP Type = 10
)

// String returns the string representation of the type.
Expand All @@ -165,6 +167,8 @@ func (t Type) String() string {
return "K8sSA"
case TypeSSHPOP:
return "SSHPOP"
case TypeSCEP:
return "SCEP"
default:
return ""
}
Expand All @@ -179,6 +183,10 @@ type SSHKeys struct {
// Config defines the default parameters used in the initialization of
// provisioners.
type Config struct {
// TODO: these probably shouldn't be here but passed via SignAuth
IntermediateCert string
SigningKey string
CACertificates []*x509.Certificate
// Claims are the default claims.
Claims Claims
// Audiences are the audiences used in the default provisioner, (JWK).
Expand Down Expand Up @@ -233,6 +241,8 @@ func (l *List) UnmarshalJSON(data []byte) error {
p = &K8sSA{}
case "sshpop":
p = &SSHPOP{}
case "scep":
p = &SCEP{}
default:
// Skip unsupported provisioners. A client using this method may be
// compiled with a version of smallstep/certificates that does not
Expand Down
116 changes: 116 additions & 0 deletions authority/provisioner/scep.go
@@ -0,0 +1,116 @@
package provisioner

import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"

"github.com/pkg/errors"
)

// SCEP is the SCEP provisioner type, an entity that can authorize the
// SCEP provisioning flow
type SCEP struct {
*base
Type string `json:"type"`
Name string `json:"name"`
// ForceCN bool `json:"forceCN,omitempty"`
// Claims *Claims `json:"claims,omitempty"`
// Options *Options `json:"options,omitempty"`
// claimer *Claimer

IntermediateCert string
SigningKey string
CACertificates []*x509.Certificate
}

// GetID returns the provisioner unique identifier.
func (s SCEP) GetID() string {
return "scep/" + s.Name
}

// GetName returns the name of the provisioner.
func (s *SCEP) GetName() string {
return s.Name
}

// GetType returns the type of provisioner.
func (s *SCEP) GetType() Type {
return TypeSCEP
}

// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
func (s *SCEP) GetEncryptedKey() (string, string, bool) {
return "", "", false
}

// GetTokenID returns the identifier of the token.
func (s *SCEP) GetTokenID(ott string) (string, error) {
return "", errors.New("scep provisioner does not implement GetTokenID")
}

// GetCACertificates returns the CA certificate chain
// TODO: this should come from the authority instead?
func (s *SCEP) GetCACertificates() []*x509.Certificate {

pemtxt, _ := ioutil.ReadFile(s.IntermediateCert) // TODO: move reading key to init? That's probably safer.
block, _ := pem.Decode([]byte(pemtxt))
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
fmt.Println(err)
}

// TODO: return chain? I'm not sure if the client understands it correctly
return []*x509.Certificate{cert}
}

func (s *SCEP) GetSigningKey() *rsa.PrivateKey {

keyBytes, err := ioutil.ReadFile(s.SigningKey)
if err != nil {
return nil
}

block, _ := pem.Decode([]byte(keyBytes))
if block == nil {
return nil
}

key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
fmt.Println(err)
return nil
}

return key
}

// Init initializes and validates the fields of a JWK type.
func (s *SCEP) Init(config Config) (err error) {

switch {
case s.Type == "":
return errors.New("provisioner type cannot be empty")
case s.Name == "":
return errors.New("provisioner name cannot be empty")
}

// // Update claims with global ones
// if p.claimer, err = NewClaimer(p.Claims, config.Claims); err != nil {
// return err
// }

s.IntermediateCert = config.IntermediateCert
s.SigningKey = config.SigningKey
s.CACertificates = config.CACertificates

return err
}

// Interface guards
var (
_ Interface = (*SCEP)(nil)
//_ scep.Provisioner = (*SCEP)(nil)
)
61 changes: 52 additions & 9 deletions ca/ca.go
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/scep"
"github.com/smallstep/certificates/server"
"github.com/smallstep/nosql"
)
Expand Down Expand Up @@ -179,17 +180,39 @@ func (ca *CA) Init(config *config.Config) (*CA, error) {
mgmtHandler.Route(r)
})
}
if ca.shouldServeSCEPEndpoints() {

// 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())
scepPrefix := "scep"
scepAuthority, err := scep.New(auth, scep.AuthorityOptions{
Service: auth.GetSCEPService(),
DNS: dns,
Prefix: scepPrefix,
})
if err != nil {
return nil, errors.Wrap(err, "error creating SCEP authority")
}
*/
scepRouterHandler := scepAPI.New(scepAuthority)

// According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10),
// SCEP operations are performed using HTTP, so that's why the API is mounted
// to the insecure mux.
insecureMux.Route("/"+scepPrefix, func(r chi.Router) {
scepRouterHandler.Route(r)
})

// The RFC also mentions usage of HTTPS, but seems to advise
// against it, because of potential interoperability issues.
// Currently I think it's not bad to use HTTPS also, so that's
// why I've kept the API endpoints in both muxes and both HTTP
// as well as HTTPS can be used to request certificates
// using SCEP.
mux.Route("/"+scepPrefix, func(r chi.Router) {
scepRouterHandler.Route(r)
})
}

// helpful routine for logging all routes //
//dumpRoutes(mux)

// Add monitoring if configured
if len(config.Monitoring) > 0 {
Expand Down Expand Up @@ -331,3 +354,23 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) {

return tlsConfig, nil
}

// shouldMountSCEPEndpoints returns if the CA should be
// configured with endpoints for SCEP. This is assumed to be
// true if a SCEPService exists, which is true in case a
// SCEP provisioner was configured.
func (ca *CA) shouldServeSCEPEndpoints() bool {
return ca.auth.GetSCEPService() != nil
}

//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())
}
}
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -13,6 +13,7 @@ require (
github.com/google/uuid v1.1.2
github.com/googleapis/gax-go/v2 v2.0.5
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/micromdm/scep v1.0.0
github.com/newrelic/go-agent v2.15.0+incompatible
github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -234,6 +234,8 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY=
github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
Expand Down

0 comments on commit 48c8671

Please sign in to comment.