diff --git a/authority/authority.go b/authority/authority.go index 6be0ea8f..a0b15649 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -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, diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 981d0b0a..3824bf6c 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -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. @@ -165,6 +167,8 @@ func (t Type) String() string { return "K8sSA" case TypeSSHPOP: return "SSHPOP" + case TypeSCEP: + return "SCEP" default: return "" } @@ -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). @@ -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 diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go new file mode 100644 index 00000000..741add16 --- /dev/null +++ b/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) +) diff --git a/ca/ca.go b/ca/ca.go index d97e0ab2..405ebe47 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -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" ) @@ -179,17 +180,39 @@ func (ca *CA) Init(config *config.Config) (*CA, error) { mgmtHandler.Route(r) }) } + if ca.shouldServeSCEPEndpoints() { + + 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 // - /* - 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()) - } - */ + //dumpRoutes(mux) // Add monitoring if configured if len(config.Monitoring) > 0 { @@ -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()) + } +} diff --git a/go.mod b/go.mod index 65b395bb..fe0c7f87 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 9529a1a3..77e0b0cb 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/scep/api.go b/scep/api.go new file mode 100644 index 00000000..1f33dfd5 --- /dev/null +++ b/scep/api.go @@ -0,0 +1,367 @@ +package scep + +import ( + "context" + "crypto" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "math/big" + "math/rand" + "net/http" + "strings" + "time" + + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/provisioner" + + microscep "github.com/micromdm/scep/scep" +) + +// Handler is the ACME request handler. +type Handler struct { + Auth Interface +} + +// New returns a new ACME API router. +func NewAPI(scepAuth Interface) api.RouterHandler { + return &Handler{scepAuth} +} + +// Route traffic and implement the Router interface. +func (h *Handler) Route(r api.Router) { + //getLink := h.Auth.GetLinkExplicit + //fmt.Println(getLink) + + //r.MethodFunc("GET", "/bla", h.baseURLFromRequest(h.lookupProvisioner(nil))) + //r.MethodFunc("GET", getLink(acme.NewNonceLink, "{provisionerID}", false, nil), h.baseURLFromRequest(h.lookupProvisioner(h.addNonce(h.GetNonce)))) + + r.MethodFunc(http.MethodGet, "/", h.lookupProvisioner(h.Get)) + r.MethodFunc(http.MethodPost, "/", h.lookupProvisioner(h.Post)) + +} + +func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { + + scepRequest, err := decodeSCEPRequest(r) + if err != nil { + fmt.Println(err) + fmt.Println("not a scep get request") + w.WriteHeader(500) + } + + scepResponse := SCEPResponse{Operation: scepRequest.Operation} + + switch scepRequest.Operation { + case opnGetCACert: + err := h.GetCACert(w, r, scepResponse) + if err != nil { + fmt.Println(err) + } + + case opnGetCACaps: + err := h.GetCACaps(w, r, scepResponse) + if err != nil { + fmt.Println(err) + } + case opnPKIOperation: + + default: + + } +} + +func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { + scepRequest, err := decodeSCEPRequest(r) + if err != nil { + fmt.Println(err) + fmt.Println("not a scep post request") + w.WriteHeader(500) + } + + scepResponse := SCEPResponse{Operation: scepRequest.Operation} + + switch scepRequest.Operation { + case opnPKIOperation: + err := h.PKIOperation(w, r, scepRequest, scepResponse) + if err != nil { + fmt.Println(err) + } + default: + + } + +} + +const maxPayloadSize = 2 << 20 + +func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { + + defer r.Body.Close() + + method := r.Method + query := r.URL.Query() + + var operation string + if _, ok := query["operation"]; ok { + operation = query.Get("operation") + } + + switch method { + case http.MethodGet: + switch operation { + case opnGetCACert, opnGetCACaps: + return SCEPRequest{ + Operation: operation, + Message: []byte{}, + }, nil + case opnPKIOperation: + var message string + if _, ok := query["message"]; ok { + message = query.Get("message") + } + decodedMessage, err := base64.URLEncoding.DecodeString(message) + if err != nil { + return SCEPRequest{}, err + } + return SCEPRequest{ + Operation: operation, + Message: decodedMessage, + }, nil + default: + return SCEPRequest{}, fmt.Errorf("unsupported operation: %s", operation) + } + case http.MethodPost: + body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize)) + if err != nil { + return SCEPRequest{}, err + } + return SCEPRequest{ + Operation: operation, + Message: body, + }, nil + default: + return SCEPRequest{}, fmt.Errorf("unsupported method: %s", method) + } +} + +type nextHTTP = func(http.ResponseWriter, *http.Request) + +// lookupProvisioner loads the provisioner associated with the request. +// Responds 404 if the provisioner does not exist. +func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // TODO: make this configurable; and we might want to look at being able to provide multiple, + // like the actual ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. + p, err := h.Auth.LoadProvisionerByID("scep/scep1") + if err != nil { + api.WriteError(w, err) + return + } + + scepProvisioner, ok := p.(*provisioner.SCEP) + if !ok { + api.WriteError(w, acme.AccountDoesNotExistErr(errors.New("provisioner must be of type SCEP"))) + return + } + + ctx = context.WithValue(ctx, acme.ProvisionerContextKey, Provisioner(scepProvisioner)) + next(w, r.WithContext(ctx)) + } +} + +func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { + + ctx := r.Context() + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + + // TODO: get the CA Certificates from the (signing) authority instead? I think that should be doable + certs := p.GetCACertificates() + + if len(certs) == 0 { + scepResponse.CACertNum = 0 + scepResponse.Err = errors.New("missing CA Cert") + } else if len(certs) == 1 { + scepResponse.Data = certs[0].Raw + scepResponse.CACertNum = 1 + } else { + data, err := microscep.DegenerateCertificates(certs) + scepResponse.Data = data + scepResponse.Err = err + } + + return writeSCEPResponse(w, scepResponse) +} + +func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { + + ctx := r.Context() + + _, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + + // TODO: get the actual capabilities from provisioner config + scepResponse.Data = formatCapabilities(defaultCapabilities) + + return writeSCEPResponse(w, scepResponse) +} + +func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { + + msg, err := microscep.ParsePKIMessage(scepRequest.Message) + if err != nil { + return err + } + + ctx := r.Context() + p, err := ProvisionerFromContext(ctx) + if err != nil { + return err + } + certs := p.GetCACertificates() + key := p.GetSigningKey() + + ca := certs[0] + if err := msg.DecryptPKIEnvelope(ca, key); err != nil { + return err + } + + if msg.MessageType == microscep.PKCSReq { + // TODO: CSR validation, like challenge password + } + + csr := msg.CSRReqMessage.CSR + id, err := createKeyIdentifier(csr.PublicKey) + if err != nil { + return err + } + + serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + + days := 40 + + template := &x509.Certificate{ + SerialNumber: serial, + Subject: csr.Subject, + NotBefore: time.Now().Add(-600).UTC(), + NotAfter: time.Now().AddDate(0, 0, days).UTC(), + SubjectKeyId: id, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + }, + SignatureAlgorithm: csr.SignatureAlgorithm, + EmailAddresses: csr.EmailAddresses, + } + + certRep, err := msg.SignCSR(ca, key, template) + if err != nil { + return err + } + + //cert := certRep.CertRepMessage.Certificate + //name := certName(cert) + + // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not + // TODO: store the new cert for CN locally + + scepResponse.Data = certRep.Raw + + return writeSCEPResponse(w, scepResponse) +} + +func certName(cert *x509.Certificate) string { + if cert.Subject.CommonName != "" { + return cert.Subject.CommonName + } + return string(cert.Signature) +} + +// createKeyIdentifier create an identifier for public keys +// according to the first method in RFC5280 section 4.2.1.2. +func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { + + keyBytes, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, err + } + + id := sha1.Sum(keyBytes) + + return id[:], nil +} + +func formatCapabilities(caps []string) []byte { + return []byte(strings.Join(caps, "\n")) +} + +// writeSCEPResponse writes a SCEP response back to the SCEP client. +func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { + if response.Err != nil { + http.Error(w, response.Err.Error(), http.StatusInternalServerError) + return nil + } + w.Header().Set("Content-Type", contentHeader(response.Operation, response.CACertNum)) + w.Write(response.Data) + return nil +} + +var ( + // TODO: check the default capabilities + 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" + pkiOpHeader = "application/x-pki-message" +) + +func contentHeader(operation string, certNum int) string { + switch operation { + case opnGetCACert: + if certNum > 1 { + return certChainHeader + } + return leafHeader + case opnPKIOperation: + return pkiOpHeader + default: + return "text/plain" + } +} + +// ProvisionerFromContext searches the context for a provisioner. Returns the +// provisioner or an error. +func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { + val := ctx.Value(acme.ProvisionerContextKey) + if val == nil { + return nil, acme.ServerInternalErr(errors.New("provisioner expected in request context")) + } + pval, ok := val.(Provisioner) + if !ok || pval == nil { + return nil, acme.ServerInternalErr(errors.New("provisioner in context is not a SCEP provisioner")) + } + return pval, nil +} diff --git a/scep/authority.go b/scep/authority.go new file mode 100644 index 00000000..9a10357c --- /dev/null +++ b/scep/authority.go @@ -0,0 +1,88 @@ +package scep + +import ( + "crypto/x509" + + "github.com/smallstep/certificates/authority/provisioner" + + "github.com/smallstep/nosql" +) + +// Interface is the SCEP authority interface. +type Interface interface { + // GetDirectory(ctx context.Context) (*Directory, error) + // NewNonce() (string, error) + // UseNonce(string) error + + // DeactivateAccount(ctx context.Context, accID string) (*Account, error) + // GetAccount(ctx context.Context, accID string) (*Account, error) + // GetAccountByKey(ctx context.Context, key *jose.JSONWebKey) (*Account, error) + // NewAccount(ctx context.Context, ao AccountOptions) (*Account, error) + // UpdateAccount(context.Context, string, []string) (*Account, error) + + // GetAuthz(ctx context.Context, accID string, authzID string) (*Authz, error) + // ValidateChallenge(ctx context.Context, accID string, chID string, key *jose.JSONWebKey) (*Challenge, error) + + // FinalizeOrder(ctx context.Context, accID string, orderID string, csr *x509.CertificateRequest) (*Order, error) + // GetOrder(ctx context.Context, accID string, orderID string) (*Order, error) + // GetOrdersByAccount(ctx context.Context, accID string) ([]string, error) + // NewOrder(ctx context.Context, oo OrderOptions) (*Order, error) + + // GetCertificate(string, string) ([]byte, error) + + LoadProvisionerByID(string) (provisioner.Interface, error) + // GetLink(ctx context.Context, linkType Link, absoluteLink bool, inputs ...string) string + // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string + + GetCACerts() ([]*x509.Certificate, error) +} + +// Authority is the layer that handles all SCEP interactions. +type Authority struct { + //certificates []*x509.Certificate + //authConfig authority.AuthConfig + backdate provisioner.Duration + db nosql.DB + // dir *directory + signAuth SignAuthority +} + +// AuthorityOptions required to create a new SCEP Authority. +type AuthorityOptions struct { + Certificates []*x509.Certificate + //AuthConfig authority.AuthConfig + 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 + // will use the Host from the request, so this value will only be used if + // request.Host is empty. + DNS string + // Prefix is a URL path prefix under which the SCEP api is served. This + // prefix is required to generate accurate SCEP links. + Prefix string +} + +// SignAuthority is the interface implemented by a CA authority. +type SignAuthority interface { + Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + LoadProvisionerByID(string) (provisioner.Interface, error) +} + +// LoadProvisionerByID calls out to the SignAuthority interface to load a +// provisioner by ID. +func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { + return a.signAuth.LoadProvisionerByID(id) +} + +func (a *Authority) GetCACerts() ([]*x509.Certificate, error) { + + // TODO: implement the SCEP authority + + return []*x509.Certificate{}, nil +} + +// Interface guards +var ( + _ Interface = (*Authority)(nil) +) diff --git a/scep/provisioner.go b/scep/provisioner.go new file mode 100644 index 00000000..4de40621 --- /dev/null +++ b/scep/provisioner.go @@ -0,0 +1,17 @@ +package scep + +import ( + "crypto/rsa" + "crypto/x509" +) + +// Provisioner is an interface that implements a subset of the provisioner.Interface -- +// only those methods required by the SCEP api/authority. +type Provisioner interface { + // AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) + // GetName() string + // DefaultTLSCertDuration() time.Duration + // GetOptions() *provisioner.Options + GetCACertificates() []*x509.Certificate + GetSigningKey() *rsa.PrivateKey +} diff --git a/scep/scep.go b/scep/scep.go new file mode 100644 index 00000000..c88027ff --- /dev/null +++ b/scep/scep.go @@ -0,0 +1,38 @@ +package scep + +import ( + database "github.com/smallstep/certificates/db" +) + +const ( + opnGetCACert = "GetCACert" + opnGetCACaps = "GetCACaps" + opnPKIOperation = "PKIOperation" +) + +// 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 { + // TODO: see ACME implementation + } + return &Authority{ + //certificates: ops.Certificates, + backdate: ops.Backdate, + db: ops.DB, + signAuth: signAuth, + }, nil +} + +// SCEPRequest is a SCEP server request. +type SCEPRequest struct { + Operation string + Message []byte +} + +// SCEPResponse is a SCEP server response. +type SCEPResponse struct { + Operation string + CACertNum int + Data []byte + Err error +}