From ffdd58ea3c0bc10a85f1e49165e698a523ca159e Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Feb 2021 12:03:08 +0100 Subject: [PATCH 01/42] Add rudimentary (and incomplete) support for SCEP --- authority/authority.go | 10 +- authority/provisioner/provisioner.go | 10 + authority/provisioner/scep.go | 116 +++++++++ ca/ca.go | 22 ++ go.mod | 1 + go.sum | 2 + scep/api.go | 367 +++++++++++++++++++++++++++ scep/authority.go | 88 +++++++ scep/provisioner.go | 17 ++ scep/scep.go | 38 +++ 10 files changed, 668 insertions(+), 3 deletions(-) create mode 100644 authority/provisioner/scep.go create mode 100644 scep/api.go create mode 100644 scep/authority.go create mode 100644 scep/provisioner.go create mode 100644 scep/scep.go diff --git a/authority/authority.go b/authority/authority.go index 4518abdf..34ccf78d 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -329,9 +329,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 aed1900a..c279bcc9 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -141,6 +141,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. @@ -164,6 +166,8 @@ func (t Type) String() string { return "K8sSA" case TypeSSHPOP: return "SSHPOP" + case TypeSCEP: + return "SCEP" default: return "" } @@ -178,6 +182,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). @@ -232,6 +240,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 3c57b759..37fb6ad4 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -18,6 +18,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" ) @@ -143,6 +144,27 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { acmeRouterHandler.Route(r) }) + // TODO: THIS SHOULDN'T HAPPEN (or should become configurable) + // Current SCEP client I'm testing with doesn't seem to easily trust untrusted certs. + tlsConfig = nil + + scepPrefix := "scep" + scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ + //Certificates: certificates, + //AuthConfig: *config.AuthorityConfig, + //Backdate: *config.AuthorityConfig.Backdate, + DB: auth.GetDatabase().(nosql.DB), + DNS: dns, + Prefix: scepPrefix, + }) + if err != nil { + return nil, errors.Wrap(err, "error creating SCEP authority") + } + scepRouterHandler := scep.NewAPI(scepAuthority) + 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 { diff --git a/go.mod b/go.mod index 5a1cd270..c81bf9dc 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,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 b61746e8..25fef498 100644 --- a/go.sum +++ b/go.sum @@ -214,6 +214,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +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 +} From 713b571d7a18a0a743654a565ab5b34fe644a5fe Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Feb 2021 17:02:39 +0100 Subject: [PATCH 02/42] Refactor SCEP authority initialization and clean some code --- authority/authority.go | 15 +++-- authority/provisioner/collection.go | 2 + authority/provisioner/provisioner.go | 4 -- authority/provisioner/scep.go | 68 +++++-------------- ca/ca.go | 15 +++-- scep/{ => api}/api.go | 69 +++++++++++++------- scep/authority.go | 98 +++++++++++++++++++++++++--- scep/provisioner.go | 13 ++-- scep/scep.go | 38 ----------- 9 files changed, 175 insertions(+), 147 deletions(-) rename scep/{ => api}/api.go (85%) delete mode 100644 scep/scep.go diff --git a/authority/authority.go b/authority/authority.go index 34ccf78d..e53144fe 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -329,13 +329,14 @@ func (a *Authority) init() error { audiences := a.config.getAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ - // 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, + // TODO: I'm not sure if extending this configuration is a good way to integrate + // It's powerful, but leaks quite some seemingly internal stuff to the provisioner. + // 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/collection.go b/authority/provisioner/collection.go index 13b7be4d..30f950a5 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -140,6 +140,8 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool) return c.Load("gcp/" + string(provisioner.Name)) case TypeACME: return c.Load("acme/" + string(provisioner.Name)) + case TypeSCEP: + return c.Load("scep/" + string(provisioner.Name)) case TypeX5C: return c.Load("x5c/" + string(provisioner.Name)) case TypeK8sSA: diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index c279bcc9..24cf98c9 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -182,10 +182,6 @@ 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). diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 741add16..30b4a1b2 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -1,11 +1,7 @@ package provisioner import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io/ioutil" + "time" "github.com/pkg/errors" ) @@ -16,14 +12,11 @@ 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 + // ForceCN bool `json:"forceCN,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer } // GetID returns the provisioner unique identifier. @@ -51,40 +44,15 @@ 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} +// GetOptions returns the configured provisioner options. +func (s *SCEP) GetOptions() *Options { + return s.Options } -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 +// DefaultTLSCertDuration returns the default TLS cert duration enforced by +// the provisioner. +func (s *SCEP) DefaultTLSCertDuration() time.Duration { + return s.claimer.DefaultTLSCertDuration() } // Init initializes and validates the fields of a JWK type. @@ -97,14 +65,10 @@ func (s *SCEP) Init(config Config) (err error) { 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 + // Update claims with global ones + if s.claimer, err = NewClaimer(s.Claims, config.Claims); err != nil { + return err + } return err } diff --git a/ca/ca.go b/ca/ca.go index 37fb6ad4..edd8bd38 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -19,6 +19,7 @@ import ( "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/scep" + scepAPI "github.com/smallstep/certificates/scep/api" "github.com/smallstep/certificates/server" "github.com/smallstep/nosql" ) @@ -150,17 +151,17 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ - //Certificates: certificates, - //AuthConfig: *config.AuthorityConfig, - //Backdate: *config.AuthorityConfig.Backdate, - DB: auth.GetDatabase().(nosql.DB), - DNS: dns, - Prefix: scepPrefix, + IntermediateCertificatePath: config.IntermediateCert, + IntermediateKeyPath: config.IntermediateKey, + Backdate: *config.AuthorityConfig.Backdate, + DB: auth.GetDatabase().(nosql.DB), + DNS: dns, + Prefix: scepPrefix, }) if err != nil { return nil, errors.Wrap(err, "error creating SCEP authority") } - scepRouterHandler := scep.NewAPI(scepAuthority) + scepRouterHandler := scepAPI.New(scepAuthority) mux.Route("/"+scepPrefix, func(r chi.Router) { scepRouterHandler.Route(r) }) diff --git a/scep/api.go b/scep/api/api.go similarity index 85% rename from scep/api.go rename to scep/api/api.go index 1f33dfd5..0e78d544 100644 --- a/scep/api.go +++ b/scep/api/api.go @@ -1,4 +1,4 @@ -package scep +package api import ( "context" @@ -19,17 +19,38 @@ import ( "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/scep" microscep "github.com/micromdm/scep/scep" ) -// Handler is the ACME request handler. -type Handler struct { - Auth Interface +const ( + opnGetCACert = "GetCACert" + opnGetCACaps = "GetCACaps" + opnPKIOperation = "PKIOperation" +) + +// SCEPRequest is a SCEP server request. +type SCEPRequest struct { + Operation string + Message []byte } -// New returns a new ACME API router. -func NewAPI(scepAuth Interface) api.RouterHandler { +// SCEPResponse is a SCEP server response. +type SCEPResponse struct { + Operation string + CACertNum int + Data []byte + Err error +} + +// Handler is the SCEP request handler. +type Handler struct { + Auth scep.Interface +} + +// New returns a new SCEP API router. +func New(scepAuth scep.Interface) api.RouterHandler { return &Handler{scepAuth} } @@ -156,7 +177,6 @@ type nextHTTP = func(http.ResponseWriter, *http.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. @@ -168,27 +188,23 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { scepProvisioner, ok := p.(*provisioner.SCEP) if !ok { - api.WriteError(w, acme.AccountDoesNotExistErr(errors.New("provisioner must be of type SCEP"))) + api.WriteError(w, errors.New("provisioner must be of type SCEP")) return } - ctx = context.WithValue(ctx, acme.ProvisionerContextKey, Provisioner(scepProvisioner)) + ctx := r.Context() + ctx = context.WithValue(ctx, acme.ProvisionerContextKey, scep.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) + certs, err := h.Auth.GetCACertificates() 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") @@ -226,13 +242,16 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque return err } - ctx := r.Context() - p, err := ProvisionerFromContext(ctx) + certs, err := h.Auth.GetCACertificates() + if err != nil { + return err + } + + // TODO: instead of getting the key to decrypt, add a decrypt function to the auth; less leaky + key, err := h.Auth.GetSigningKey() if err != nil { return err } - certs := p.GetCACertificates() - key := p.GetSigningKey() ca := certs[0] if err := msg.DecryptPKIEnvelope(ca, key); err != nil { @@ -276,10 +295,12 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque //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 + // TODO: store the new cert for CN locally; should go into the DB scepResponse.Data = certRep.Raw + api.LogCertificate(w, certRep.Certificate) + return writeSCEPResponse(w, scepResponse) } @@ -354,14 +375,14 @@ func contentHeader(operation string, certNum int) string { // ProvisionerFromContext searches the context for a provisioner. Returns the // provisioner or an error. -func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { +func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { val := ctx.Value(acme.ProvisionerContextKey) if val == nil { - return nil, acme.ServerInternalErr(errors.New("provisioner expected in request context")) + return nil, errors.New("provisioner expected in request context") } - pval, ok := val.(Provisioner) + pval, ok := val.(scep.Provisioner) if !ok || pval == nil { - return nil, acme.ServerInternalErr(errors.New("provisioner in context is not a SCEP provisioner")) + return nil, errors.New("provisioner in context is not a SCEP provisioner") } return pval, nil } diff --git a/scep/authority.go b/scep/authority.go index 9a10357c..27817b0f 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,9 +1,15 @@ package scep import ( + "crypto/rsa" "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" "github.com/smallstep/certificates/authority/provisioner" + database "github.com/smallstep/certificates/db" + "go.step.sm/crypto/pemutil" "github.com/smallstep/nosql" ) @@ -34,23 +40,33 @@ type Interface interface { // 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) + GetCACertificates() ([]*x509.Certificate, error) + GetSigningKey() (*rsa.PrivateKey, 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 + prefix string + dns string + // dir *directory + + intermediateCertificate *x509.Certificate + intermediateKey *rsa.PrivateKey + + //signer crypto.Signer + signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - Certificates []*x509.Certificate - //AuthConfig authority.AuthConfig + IntermediateCertificatePath string + IntermediateKeyPath string + + // Backdate Backdate provisioner.Duration // DB is the database used by nosql. DB nosql.DB @@ -69,17 +85,83 @@ type SignAuthority interface { LoadProvisionerByID(string) (provisioner.Interface, error) } +// 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 + } + + // TODO: the below is a bit similar as what happens in the core Authority class, which + // creates the full x509 service. However, those aren't accessible directly, which is + // why I reimplemented this (for now). There might be an alternative that I haven't + // found yet. + certificateChain, err := pemutil.ReadCertificateBundle(ops.IntermediateCertificatePath) + if err != nil { + return nil, err + } + + intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath) + if err != nil { + return nil, err + } + + return &Authority{ + backdate: ops.Backdate, + db: ops.DB, + prefix: ops.Prefix, + dns: ops.DNS, + intermediateCertificate: certificateChain[0], + intermediateKey: intermediateKey, + signAuth: signAuth, + }, nil +} + +func readPrivateKey(path string) (*rsa.PrivateKey, error) { + + keyBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + block, _ := pem.Decode([]byte(keyBytes)) + if block == nil { + return nil, nil + } + + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return key, nil +} + // 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) { +// GetCACertificates returns the certificate (chain) for the CA +func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { - // TODO: implement the SCEP authority + if a.intermediateCertificate == nil { + return nil, errors.New("no intermediate certificate available in SCEP authority") + } - return []*x509.Certificate{}, nil + return []*x509.Certificate{a.intermediateCertificate}, nil +} + +// GetSigningKey returns the RSA private key for the CA +// TODO: we likely should provide utility functions for decrypting and +// signing instead of providing the signing key directly +func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) { + + if a.intermediateKey == nil { + return nil, errors.New("no intermediate key available in SCEP authority") + } + + return a.intermediateKey, nil } // Interface guards diff --git a/scep/provisioner.go b/scep/provisioner.go index 4de40621..d543d453 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -1,17 +1,16 @@ package scep import ( - "crypto/rsa" - "crypto/x509" + "time" + + "github.com/smallstep/certificates/authority/provisioner" ) // 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 + GetName() string + DefaultTLSCertDuration() time.Duration + GetOptions() *provisioner.Options } diff --git a/scep/scep.go b/scep/scep.go deleted file mode 100644 index c88027ff..00000000 --- a/scep/scep.go +++ /dev/null @@ -1,38 +0,0 @@ -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 -} From 78d78580b2ce5733c2239a934aba16b177ba96a7 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 19 Feb 2021 11:00:52 +0100 Subject: [PATCH 03/42] Add note about using a second (unsecured) server --- ca/ca.go | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index a061fa02..f256a5e4 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -147,6 +147,13 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { // TODO: THIS SHOULDN'T HAPPEN (or should become configurable) // Current SCEP client I'm testing with doesn't seem to easily trust untrusted certs. + // Idea: provide a second mux/handler that runs without TLS. It probably should only + // have routes that are intended to be ran without TLS, like the SCEP ones. Look into + // option to not enable it in case no SCEP providers are configured. It might + // be nice to still include the SCEP routes in the secure handler too, for + // client that do understand HTTPS. The RFC does not seem to explicitly exclude HTTPS + // usage, but it mentions some caveats related to managing web PKI certificates as + // well as certificates via SCEP. tlsConfig = nil scepPrefix := "scep" @@ -166,16 +173,8 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { 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()) - } - */ + // helpful routine for logging all routes // + //dumpRoutes(mux) // Add monitoring if configured if len(config.Monitoring) > 0 { @@ -316,3 +315,14 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { return tlsConfig, nil } + +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()) + } +} From 165f6a1ccdd0dc4b6b5300d909d0730219d8d0fe Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 19 Feb 2021 11:01:19 +0100 Subject: [PATCH 04/42] Improve setup for multiple SCEP providers (slightly) --- scep/api/api.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index 0e78d544..7ac80cdb 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -178,9 +178,18 @@ type nextHTTP = func(http.ResponseWriter, *http.Request) func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { + // name := chi.URLParam(r, "provisionerID") + // provisionerID, err := url.PathUnescape(name) + // if err != nil { + // api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + // return + // } + // 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") + // like the ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. + provisionerID := "scep1" + + p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { api.WriteError(w, err) return From 702032f2b7b51cdc400797abb354c8070ab92224 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 19 Feb 2021 12:06:24 +0100 Subject: [PATCH 05/42] Add number of certs to return and fix CR LF in CACaps --- scep/api/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index 7ac80cdb..5a4cb5b2 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -222,6 +222,7 @@ func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse scepResponse.CACertNum = 1 } else { data, err := microscep.DegenerateCertificates(certs) + scepResponse.CACertNum = len(certs) scepResponse.Data = data scepResponse.Err = err } @@ -335,7 +336,7 @@ func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { } func formatCapabilities(caps []string) []byte { - return []byte(strings.Join(caps, "\n")) + return []byte(strings.Join(caps, "\r\n")) } // writeSCEPResponse writes a SCEP response back to the SCEP client. @@ -350,7 +351,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { } var ( - // TODO: check the default capabilities + // TODO: check the default capabilities; https://tools.ietf.org/html/rfc8894#section-3.5.2 defaultCapabilities = []string{ "Renewal", "SHA-1", From f871f8135cf4c0597ca5e0536c69186352f49411 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 25 Feb 2021 22:28:08 +0100 Subject: [PATCH 06/42] Add full copy of mozilla/pkcs7 module as internal dependency The full contents of the git repository @432b2356ecb... was copied. Only go.mod was removed from it. --- scep/pkcs7/.gitignore | 24 ++ scep/pkcs7/.travis.yml | 10 + scep/pkcs7/LICENSE | 22 ++ scep/pkcs7/Makefile | 20 ++ scep/pkcs7/README.md | 69 ++++ scep/pkcs7/ber.go | 251 +++++++++++++ scep/pkcs7/ber_test.go | 62 ++++ scep/pkcs7/decrypt.go | 177 +++++++++ scep/pkcs7/decrypt_test.go | 60 ++++ scep/pkcs7/encrypt.go | 399 +++++++++++++++++++++ scep/pkcs7/encrypt_test.go | 102 ++++++ scep/pkcs7/pkcs7.go | 291 +++++++++++++++ scep/pkcs7/pkcs7_test.go | 326 +++++++++++++++++ scep/pkcs7/sign.go | 429 ++++++++++++++++++++++ scep/pkcs7/sign_test.go | 266 ++++++++++++++ scep/pkcs7/verify.go | 264 ++++++++++++++ scep/pkcs7/verify_test.go | 713 +++++++++++++++++++++++++++++++++++++ 17 files changed, 3485 insertions(+) create mode 100644 scep/pkcs7/.gitignore create mode 100644 scep/pkcs7/.travis.yml create mode 100644 scep/pkcs7/LICENSE create mode 100644 scep/pkcs7/Makefile create mode 100644 scep/pkcs7/README.md create mode 100644 scep/pkcs7/ber.go create mode 100644 scep/pkcs7/ber_test.go create mode 100644 scep/pkcs7/decrypt.go create mode 100644 scep/pkcs7/decrypt_test.go create mode 100644 scep/pkcs7/encrypt.go create mode 100644 scep/pkcs7/encrypt_test.go create mode 100644 scep/pkcs7/pkcs7.go create mode 100644 scep/pkcs7/pkcs7_test.go create mode 100644 scep/pkcs7/sign.go create mode 100644 scep/pkcs7/sign_test.go create mode 100644 scep/pkcs7/verify.go create mode 100644 scep/pkcs7/verify_test.go diff --git a/scep/pkcs7/.gitignore b/scep/pkcs7/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/scep/pkcs7/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/scep/pkcs7/.travis.yml b/scep/pkcs7/.travis.yml new file mode 100644 index 00000000..eac4c176 --- /dev/null +++ b/scep/pkcs7/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - "1.11" + - "1.12" + - "1.13" + - tip +before_install: + - make gettools +script: + - make diff --git a/scep/pkcs7/LICENSE b/scep/pkcs7/LICENSE new file mode 100644 index 00000000..75f32090 --- /dev/null +++ b/scep/pkcs7/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Andrew Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/scep/pkcs7/Makefile b/scep/pkcs7/Makefile new file mode 100644 index 00000000..47c73b86 --- /dev/null +++ b/scep/pkcs7/Makefile @@ -0,0 +1,20 @@ +all: vet staticcheck test + +test: + go test -covermode=count -coverprofile=coverage.out . + +showcoverage: test + go tool cover -html=coverage.out + +vet: + go vet . + +lint: + golint . + +staticcheck: + staticcheck . + +gettools: + go get -u honnef.co/go/tools/... + go get -u golang.org/x/lint/golint diff --git a/scep/pkcs7/README.md b/scep/pkcs7/README.md new file mode 100644 index 00000000..bf37059c --- /dev/null +++ b/scep/pkcs7/README.md @@ -0,0 +1,69 @@ +# pkcs7 + +[![GoDoc](https://godoc.org/go.mozilla.org/pkcs7?status.svg)](https://godoc.org/go.mozilla.org/pkcs7) +[![Build Status](https://travis-ci.org/mozilla-services/pkcs7.svg?branch=master)](https://travis-ci.org/mozilla-services/pkcs7) + +pkcs7 implements parsing and creating signed and enveloped messages. + +```go +package main + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + + "go.mozilla.org/pkcs7" +) + +func SignAndDetach(content []byte, cert *x509.Certificate, privkey *rsa.PrivateKey) (signed []byte, err error) { + toBeSigned, err := NewSignedData(content) + if err != nil { + err = fmt.Errorf("Cannot initialize signed data: %s", err) + return + } + if err = toBeSigned.AddSigner(cert, privkey, SignerInfoConfig{}); err != nil { + err = fmt.Errorf("Cannot add signer: %s", err) + return + } + + // Detach signature, omit if you want an embedded signature + toBeSigned.Detach() + + signed, err = toBeSigned.Finish() + if err != nil { + err = fmt.Errorf("Cannot finish signing data: %s", err) + return + } + + // Verify the signature + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) + p7, err := pkcs7.Parse(signed) + if err != nil { + err = fmt.Errorf("Cannot parse our signed data: %s", err) + return + } + + // since the signature was detached, reattach the content here + p7.Content = content + + if bytes.Compare(content, p7.Content) != 0 { + err = fmt.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content) + return + } + if err = p7.Verify(); err != nil { + err = fmt.Errorf("Cannot verify our signed data: %s", err) + return + } + + return signed, nil +} +``` + + + +## Credits +This is a fork of [fullsailor/pkcs7](https://github.com/fullsailor/pkcs7) diff --git a/scep/pkcs7/ber.go b/scep/pkcs7/ber.go new file mode 100644 index 00000000..58525673 --- /dev/null +++ b/scep/pkcs7/ber.go @@ -0,0 +1,251 @@ +package pkcs7 + +import ( + "bytes" + "errors" +) + +var encodeIndent = 0 + +type asn1Object interface { + EncodeTo(writer *bytes.Buffer) error +} + +type asn1Structured struct { + tagBytes []byte + content []asn1Object +} + +func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { + //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) + encodeIndent++ + inner := new(bytes.Buffer) + for _, obj := range s.content { + err := obj.EncodeTo(inner) + if err != nil { + return err + } + } + encodeIndent-- + out.Write(s.tagBytes) + encodeLength(out, inner.Len()) + out.Write(inner.Bytes()) + return nil +} + +type asn1Primitive struct { + tagBytes []byte + length int + content []byte +} + +func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { + _, err := out.Write(p.tagBytes) + if err != nil { + return err + } + if err = encodeLength(out, p.length); err != nil { + return err + } + //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) + //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) + out.Write(p.content) + + return nil +} + +func ber2der(ber []byte) ([]byte, error) { + if len(ber) == 0 { + return nil, errors.New("ber2der: input ber is empty") + } + //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) + out := new(bytes.Buffer) + + obj, _, err := readObject(ber, 0) + if err != nil { + return nil, err + } + obj.EncodeTo(out) + + // if offset < len(ber) { + // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) + //} + + return out.Bytes(), nil +} + +// encodes lengths that are longer than 127 into string of bytes +func marshalLongLength(out *bytes.Buffer, i int) (err error) { + n := lengthLength(i) + + for ; n > 0; n-- { + err = out.WriteByte(byte(i >> uint((n-1)*8))) + if err != nil { + return + } + } + + return nil +} + +// computes the byte length of an encoded length value +func lengthLength(i int) (numBytes int) { + numBytes = 1 + for i > 255 { + numBytes++ + i >>= 8 + } + return +} + +// encodes the length in DER format +// If the length fits in 7 bits, the value is encoded directly. +// +// Otherwise, the number of bytes to encode the length is first determined. +// This number is likely to be 4 or less for a 32bit length. This number is +// added to 0x80. The length is encoded in big endian encoding follow after +// +// Examples: +// length | byte 1 | bytes n +// 0 | 0x00 | - +// 120 | 0x78 | - +// 200 | 0x81 | 0xC8 +// 500 | 0x82 | 0x01 0xF4 +// +func encodeLength(out *bytes.Buffer, length int) (err error) { + if length >= 128 { + l := lengthLength(length) + err = out.WriteByte(0x80 | byte(l)) + if err != nil { + return + } + err = marshalLongLength(out, length) + if err != nil { + return + } + } else { + err = out.WriteByte(byte(length)) + if err != nil { + return + } + } + return +} + +func readObject(ber []byte, offset int) (asn1Object, int, error) { + berLen := len(ber) + if offset >= berLen { + return nil, 0, errors.New("ber2der: offset is after end of ber data") + } + tagStart := offset + b := ber[offset] + offset++ + if offset >= berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + tag := b & 0x1F // last 5 bits + if tag == 0x1F { + tag = 0 + for ber[offset] >= 0x80 { + tag = tag*128 + ber[offset] - 0x80 + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + // jvehent 20170227: this doesn't appear to be used anywhere... + //tag = tag*128 + ber[offset] - 0x80 + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + tagEnd := offset + + kind := b & 0x20 + if kind == 0 { + debugprint("--> Primitive\n") + } else { + debugprint("--> Constructed\n") + } + // read length + var length int + l := ber[offset] + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + hack := 0 + if l > 0x80 { + numberOfBytes := (int)(l & 0x7F) + if numberOfBytes > 4 { // int is only guaranteed to be 32bit + return nil, 0, errors.New("ber2der: BER tag length too long") + } + if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { + return nil, 0, errors.New("ber2der: BER tag length is negative") + } + if (int)(ber[offset]) == 0x0 { + return nil, 0, errors.New("ber2der: BER tag length has leading zero") + } + debugprint("--> (compute length) indicator byte: %x\n", l) + debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) + for i := 0; i < numberOfBytes; i++ { + length = length*256 + (int)(ber[offset]) + offset++ + if offset > berLen { + return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") + } + } + } else if l == 0x80 { + // find length by searching content + markerIndex := bytes.LastIndex(ber[offset:], []byte{0x0, 0x0}) + if markerIndex == -1 { + return nil, 0, errors.New("ber2der: Invalid BER format") + } + length = markerIndex + hack = 2 + debugprint("--> (compute length) marker found at offset: %d\n", markerIndex+offset) + } else { + length = (int)(l) + } + if length < 0 { + return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length") + } + //fmt.Printf("--> length : %d\n", length) + contentEnd := offset + length + if contentEnd > len(ber) { + return nil, 0, errors.New("ber2der: BER tag length is more than available data") + } + debugprint("--> content start : %d\n", offset) + debugprint("--> content end : %d\n", contentEnd) + debugprint("--> content : % X\n", ber[offset:contentEnd]) + var obj asn1Object + if kind == 0 { + obj = asn1Primitive{ + tagBytes: ber[tagStart:tagEnd], + length: length, + content: ber[offset:contentEnd], + } + } else { + var subObjects []asn1Object + for offset < contentEnd { + var subObj asn1Object + var err error + subObj, offset, err = readObject(ber[:contentEnd], offset) + if err != nil { + return nil, 0, err + } + subObjects = append(subObjects, subObj) + } + obj = asn1Structured{ + tagBytes: ber[tagStart:tagEnd], + content: subObjects, + } + } + + return obj, contentEnd + hack, nil +} + +func debugprint(format string, a ...interface{}) { + //fmt.Printf(format, a) +} diff --git a/scep/pkcs7/ber_test.go b/scep/pkcs7/ber_test.go new file mode 100644 index 00000000..fcb4b6a2 --- /dev/null +++ b/scep/pkcs7/ber_test.go @@ -0,0 +1,62 @@ +package pkcs7 + +import ( + "bytes" + "encoding/asn1" + "strings" + "testing" +) + +func TestBer2Der(t *testing.T) { + // indefinite length fixture + ber := []byte{0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00} + expected := []byte{0x30, 0x03, 0x02, 0x01, 0x01} + der, err := ber2der(ber) + if err != nil { + t.Fatalf("ber2der failed with error: %v", err) + } + if !bytes.Equal(der, expected) { + t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der) + } + + if der2, err := ber2der(der); err != nil { + t.Errorf("ber2der on DER bytes failed with error: %v", err) + } else { + if !bytes.Equal(der, der2) { + t.Error("ber2der is not idempotent") + } + } + var thing struct { + Number int + } + rest, err := asn1.Unmarshal(der, &thing) + if err != nil { + t.Errorf("Cannot parse resulting DER because: %v", err) + } else if len(rest) > 0 { + t.Errorf("Resulting DER has trailing data: % X", rest) + } +} + +func TestBer2Der_Negatives(t *testing.T) { + fixtures := []struct { + Input []byte + ErrorContains string + }{ + {[]byte{0x30, 0x85}, "tag length too long"}, + {[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"}, + {[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"}, + {[]byte{0x30, 0x80, 0x1, 0x2}, "Invalid BER format"}, + {[]byte{0x30, 0x03, 0x01, 0x02}, "length is more than available data"}, + {[]byte{0x30}, "end of ber data reached"}, + } + + for _, fixture := range fixtures { + _, err := ber2der(fixture.Input) + if err == nil { + t.Errorf("No error thrown. Expected: %s", fixture.ErrorContains) + } + if !strings.Contains(err.Error(), fixture.ErrorContains) { + t.Errorf("Unexpected error thrown.\n\tExpected: /%s/\n\tActual: %s", fixture.ErrorContains, err.Error()) + } + } +} diff --git a/scep/pkcs7/decrypt.go b/scep/pkcs7/decrypt.go new file mode 100644 index 00000000..0d088d62 --- /dev/null +++ b/scep/pkcs7/decrypt.go @@ -0,0 +1,177 @@ +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "errors" + "fmt" +) + +// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed +var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") + +// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data +var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") + +// Decrypt decrypts encrypted content info for recipient cert and private key +func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pkey crypto.PrivateKey) ([]byte, error) { + data, ok := p7.raw.(envelopedData) + if !ok { + return nil, ErrNotEncryptedContent + } + recipient := selectRecipientForCertificate(data.RecipientInfos, cert) + if recipient.EncryptedKey == nil { + return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") + } + switch pkey := pkey.(type) { + case *rsa.PrivateKey: + var contentKey []byte + contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, pkey, recipient.EncryptedKey) + if err != nil { + return nil, err + } + return data.EncryptedContentInfo.decrypt(contentKey) + } + return nil, ErrUnsupportedAlgorithm +} + +// DecryptUsingPSK decrypts encrypted data using caller provided +// pre-shared secret +func (p7 *PKCS7) DecryptUsingPSK(key []byte) ([]byte, error) { + data, ok := p7.raw.(encryptedData) + if !ok { + return nil, ErrNotEncryptedContent + } + return data.EncryptedContentInfo.decrypt(key) +} + +func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { + alg := eci.ContentEncryptionAlgorithm.Algorithm + if !alg.Equal(OIDEncryptionAlgorithmDESCBC) && + !alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES256CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES128CBC) && + !alg.Equal(OIDEncryptionAlgorithmAES128GCM) && + !alg.Equal(OIDEncryptionAlgorithmAES256GCM) { + fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) + return nil, ErrUnsupportedAlgorithm + } + + // EncryptedContent can either be constructed of multple OCTET STRINGs + // or _be_ a tagged OCTET STRING + var cyphertext []byte + if eci.EncryptedContent.IsCompound { + // Complex case to concat all of the children OCTET STRINGs + var buf bytes.Buffer + cypherbytes := eci.EncryptedContent.Bytes + for { + var part []byte + cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) + buf.Write(part) + if cypherbytes == nil { + break + } + } + cyphertext = buf.Bytes() + } else { + // Simple case, the bytes _are_ the cyphertext + cyphertext = eci.EncryptedContent.Bytes + } + + var block cipher.Block + var err error + + switch { + case alg.Equal(OIDEncryptionAlgorithmDESCBC): + block, err = des.NewCipher(key) + case alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC): + block, err = des.NewTripleDESCipher(key) + case alg.Equal(OIDEncryptionAlgorithmAES256CBC), alg.Equal(OIDEncryptionAlgorithmAES256GCM): + fallthrough + case alg.Equal(OIDEncryptionAlgorithmAES128GCM), alg.Equal(OIDEncryptionAlgorithmAES128CBC): + block, err = aes.NewCipher(key) + } + + if err != nil { + return nil, err + } + + if alg.Equal(OIDEncryptionAlgorithmAES128GCM) || alg.Equal(OIDEncryptionAlgorithmAES256GCM) { + params := aesGCMParameters{} + paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes + + _, err := asn1.Unmarshal(paramBytes, ¶ms) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if len(params.Nonce) != gcm.NonceSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + if params.ICVLen != gcm.Overhead() { + return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") + } + + plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil + } + + iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes + if len(iv) != block.BlockSize() { + return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") + } + mode := cipher.NewCBCDecrypter(block, iv) + plaintext := make([]byte, len(cyphertext)) + mode.CryptBlocks(plaintext, cyphertext) + if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { + return nil, err + } + return plaintext, nil +} + +func unpad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + if len(data)%blocklen != 0 || len(data) == 0 { + return nil, fmt.Errorf("invalid data len %d", len(data)) + } + + // the last byte is the length of padding + padlen := int(data[len(data)-1]) + + // check padding integrity, all bytes should be the same + pad := data[len(data)-padlen:] + for _, padbyte := range pad { + if padbyte != byte(padlen) { + return nil, errors.New("invalid padding") + } + } + + return data[:len(data)-padlen], nil +} + +func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { + for _, recp := range recipients { + if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { + return recp + } + } + return recipientInfo{} +} diff --git a/scep/pkcs7/decrypt_test.go b/scep/pkcs7/decrypt_test.go new file mode 100644 index 00000000..06cc0f80 --- /dev/null +++ b/scep/pkcs7/decrypt_test.go @@ -0,0 +1,60 @@ +package pkcs7 + +import ( + "bytes" + "testing" +) + +func TestDecrypt(t *testing.T) { + fixture := UnmarshalTestFixture(EncryptedTestFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Fatal(err) + } + content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey) + if err != nil { + t.Errorf("Cannot Decrypt with error: %v", err) + } + expected := []byte("This is a test") + if !bytes.Equal(content, expected) { + t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content) + } +} + +// Content is "This is a test" +var EncryptedTestFixture = ` +-----BEGIN PKCS7----- +MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK +EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3 +DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G +dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO +8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3 +DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g== +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj +bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x +NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT +bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc +LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg +8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP ++Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI +hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18 +sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n +9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy +yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe +KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB +AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu +MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d +H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C +67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv +Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV +i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD +6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA +o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b +dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy +KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA== +-----END PRIVATE KEY-----` diff --git a/scep/pkcs7/encrypt.go b/scep/pkcs7/encrypt.go new file mode 100644 index 00000000..da57ae64 --- /dev/null +++ b/scep/pkcs7/encrypt.go @@ -0,0 +1,399 @@ +package pkcs7 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" +) + +type envelopedData struct { + Version int + RecipientInfos []recipientInfo `asn1:"set"` + EncryptedContentInfo encryptedContentInfo +} + +type encryptedData struct { + Version int + EncryptedContentInfo encryptedContentInfo +} + +type recipientInfo struct { + Version int + IssuerAndSerialNumber issuerAndSerial + KeyEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedKey []byte +} + +type encryptedContentInfo struct { + ContentType asn1.ObjectIdentifier + ContentEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"` +} + +const ( + // EncryptionAlgorithmDESCBC is the DES CBC encryption algorithm + EncryptionAlgorithmDESCBC = iota + + // EncryptionAlgorithmAES128CBC is the AES 128 bits with CBC encryption algorithm + // Avoid this algorithm unless required for interoperability; use AES GCM instead. + EncryptionAlgorithmAES128CBC + + // EncryptionAlgorithmAES256CBC is the AES 256 bits with CBC encryption algorithm + // Avoid this algorithm unless required for interoperability; use AES GCM instead. + EncryptionAlgorithmAES256CBC + + // EncryptionAlgorithmAES128GCM is the AES 128 bits with GCM encryption algorithm + EncryptionAlgorithmAES128GCM + + // EncryptionAlgorithmAES256GCM is the AES 256 bits with GCM encryption algorithm + EncryptionAlgorithmAES256GCM +) + +// ContentEncryptionAlgorithm determines the algorithm used to encrypt the +// plaintext message. Change the value of this variable to change which +// algorithm is used in the Encrypt() function. +var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC + +// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt +// content with an unsupported algorithm. +var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC, AES-CBC, and AES-GCM supported") + +// ErrPSKNotProvided is returned when attempting to encrypt +// using a PSK without actually providing the PSK. +var ErrPSKNotProvided = errors.New("pkcs7: cannot encrypt content: PSK not provided") + +const nonceSize = 12 + +type aesGCMParameters struct { + Nonce []byte `asn1:"tag:4"` + ICVLen int +} + +func encryptAESGCM(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + var keyLen int + var algID asn1.ObjectIdentifier + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmAES128GCM: + keyLen = 16 + algID = OIDEncryptionAlgorithmAES128GCM + case EncryptionAlgorithmAES256GCM: + keyLen = 32 + algID = OIDEncryptionAlgorithmAES256GCM + default: + return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESGCM: %d", ContentEncryptionAlgorithm) + } + if key == nil { + // Create AES key + key = make([]byte, keyLen) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create nonce + nonce := make([]byte, nonceSize) + + _, err := rand.Read(nonce) + if err != nil { + return nil, nil, err + } + + // Encrypt content + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, nil, err + } + + ciphertext := gcm.Seal(nil, nonce, content, nil) + + // Prepare ASN.1 Encrypted Content Info + paramSeq := aesGCMParameters{ + Nonce: nonce, + ICVLen: gcm.Overhead(), + } + + paramBytes, err := asn1.Marshal(paramSeq) + if err != nil { + return nil, nil, err + } + + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: algID, + Parameters: asn1.RawValue{ + Tag: asn1.TagSequence, + Bytes: paramBytes, + }, + }, + EncryptedContent: marshalEncryptedContent(ciphertext), + } + + return key, &eci, nil +} + +func encryptDESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + if key == nil { + // Create DES key + key = make([]byte, 8) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create CBC IV + iv := make([]byte, des.BlockSize) + _, err := rand.Read(iv) + if err != nil { + return nil, nil, err + } + + // Encrypt padded content + block, err := des.NewCipher(key) + if err != nil { + return nil, nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + plaintext, err := pad(content, mode.BlockSize()) + if err != nil { + return nil, nil, err + } + cyphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(cyphertext, plaintext) + + // Prepare ASN.1 Encrypted Content Info + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: OIDEncryptionAlgorithmDESCBC, + Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, + }, + EncryptedContent: marshalEncryptedContent(cyphertext), + } + + return key, &eci, nil +} + +func encryptAESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { + var keyLen int + var algID asn1.ObjectIdentifier + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmAES128CBC: + keyLen = 16 + algID = OIDEncryptionAlgorithmAES128CBC + case EncryptionAlgorithmAES256CBC: + keyLen = 32 + algID = OIDEncryptionAlgorithmAES256CBC + default: + return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESCBC: %d", ContentEncryptionAlgorithm) + } + + if key == nil { + // Create AES key + key = make([]byte, keyLen) + + _, err := rand.Read(key) + if err != nil { + return nil, nil, err + } + } + + // Create CBC IV + iv := make([]byte, aes.BlockSize) + _, err := rand.Read(iv) + if err != nil { + return nil, nil, err + } + + // Encrypt padded content + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + plaintext, err := pad(content, mode.BlockSize()) + if err != nil { + return nil, nil, err + } + cyphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(cyphertext, plaintext) + + // Prepare ASN.1 Encrypted Content Info + eci := encryptedContentInfo{ + ContentType: OIDData, + ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: algID, + Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, + }, + EncryptedContent: marshalEncryptedContent(cyphertext), + } + + return key, &eci, nil +} + +// Encrypt creates and returns an envelope data PKCS7 structure with encrypted +// recipient keys for each recipient public key. +// +// The algorithm used to perform encryption is determined by the current value +// of the global ContentEncryptionAlgorithm package variable. By default, the +// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the +// value before calling Encrypt(). For example: +// +// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM +// +// TODO(fullsailor): Add support for encrypting content with other algorithms +func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) { + var eci *encryptedContentInfo + var key []byte + var err error + + // Apply chosen symmetric encryption method + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmDESCBC: + key, eci, err = encryptDESCBC(content, nil) + case EncryptionAlgorithmAES128CBC: + fallthrough + case EncryptionAlgorithmAES256CBC: + key, eci, err = encryptAESCBC(content, nil) + case EncryptionAlgorithmAES128GCM: + fallthrough + case EncryptionAlgorithmAES256GCM: + key, eci, err = encryptAESGCM(content, nil) + + default: + return nil, ErrUnsupportedEncryptionAlgorithm + } + + if err != nil { + return nil, err + } + + // Prepare each recipient's encrypted cipher key + recipientInfos := make([]recipientInfo, len(recipients)) + for i, recipient := range recipients { + encrypted, err := encryptKey(key, recipient) + if err != nil { + return nil, err + } + ias, err := cert2issuerAndSerial(recipient) + if err != nil { + return nil, err + } + info := recipientInfo{ + Version: 0, + IssuerAndSerialNumber: ias, + KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: OIDEncryptionAlgorithmRSA, + }, + EncryptedKey: encrypted, + } + recipientInfos[i] = info + } + + // Prepare envelope content + envelope := envelopedData{ + EncryptedContentInfo: *eci, + Version: 0, + RecipientInfos: recipientInfos, + } + innerContent, err := asn1.Marshal(envelope) + if err != nil { + return nil, err + } + + // Prepare outer payload structure + wrapper := contentInfo{ + ContentType: OIDEnvelopedData, + Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, + } + + return asn1.Marshal(wrapper) +} + +// EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, +// encrypted using caller provided pre-shared secret. +func EncryptUsingPSK(content []byte, key []byte) ([]byte, error) { + var eci *encryptedContentInfo + var err error + + if key == nil { + return nil, ErrPSKNotProvided + } + + // Apply chosen symmetric encryption method + switch ContentEncryptionAlgorithm { + case EncryptionAlgorithmDESCBC: + _, eci, err = encryptDESCBC(content, key) + + case EncryptionAlgorithmAES128GCM: + fallthrough + case EncryptionAlgorithmAES256GCM: + _, eci, err = encryptAESGCM(content, key) + + default: + return nil, ErrUnsupportedEncryptionAlgorithm + } + + if err != nil { + return nil, err + } + + // Prepare encrypted-data content + ed := encryptedData{ + Version: 0, + EncryptedContentInfo: *eci, + } + innerContent, err := asn1.Marshal(ed) + if err != nil { + return nil, err + } + + // Prepare outer payload structure + wrapper := contentInfo{ + ContentType: OIDEncryptedData, + Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, + } + + return asn1.Marshal(wrapper) +} + +func marshalEncryptedContent(content []byte) asn1.RawValue { + asn1Content, _ := asn1.Marshal(content) + return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} +} + +func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { + if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { + return rsa.EncryptPKCS1v15(rand.Reader, pub, key) + } + return nil, ErrUnsupportedAlgorithm +} + +func pad(data []byte, blocklen int) ([]byte, error) { + if blocklen < 1 { + return nil, fmt.Errorf("invalid blocklen %d", blocklen) + } + padlen := blocklen - (len(data) % blocklen) + if padlen == 0 { + padlen = blocklen + } + pad := bytes.Repeat([]byte{byte(padlen)}, padlen) + return append(data, pad...), nil +} diff --git a/scep/pkcs7/encrypt_test.go b/scep/pkcs7/encrypt_test.go new file mode 100644 index 00000000..c64381e2 --- /dev/null +++ b/scep/pkcs7/encrypt_test.go @@ -0,0 +1,102 @@ +package pkcs7 + +import ( + "bytes" + "crypto/x509" + "testing" +) + +func TestEncrypt(t *testing.T) { + modes := []int{ + EncryptionAlgorithmDESCBC, + EncryptionAlgorithmAES128CBC, + EncryptionAlgorithmAES256CBC, + EncryptionAlgorithmAES128GCM, + EncryptionAlgorithmAES256GCM, + } + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + } + for _, mode := range modes { + for _, sigalg := range sigalgs { + ContentEncryptionAlgorithm = mode + + plaintext := []byte("Hello Secret World!") + cert, err := createTestCertificate(sigalg) + if err != nil { + t.Fatal(err) + } + encrypted, err := Encrypt(plaintext, []*x509.Certificate{cert.Certificate}) + if err != nil { + t.Fatal(err) + } + p7, err := Parse(encrypted) + if err != nil { + t.Fatalf("cannot Parse encrypted result: %s", err) + } + result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } + } +} + +func TestEncryptUsingPSK(t *testing.T) { + modes := []int{ + EncryptionAlgorithmDESCBC, + EncryptionAlgorithmAES128GCM, + } + + for _, mode := range modes { + ContentEncryptionAlgorithm = mode + plaintext := []byte("Hello Secret World!") + var key []byte + + switch mode { + case EncryptionAlgorithmDESCBC: + key = []byte("64BitKey") + case EncryptionAlgorithmAES128GCM: + key = []byte("128BitKey4AESGCM") + } + ciphertext, err := EncryptUsingPSK(plaintext, key) + if err != nil { + t.Fatal(err) + } + + p7, _ := Parse(ciphertext) + result, err := p7.DecryptUsingPSK(key) + if err != nil { + t.Fatalf("cannot Decrypt encrypted result: %s", err) + } + if !bytes.Equal(plaintext, result) { + t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) + } + } +} + +func TestPad(t *testing.T) { + tests := []struct { + Original []byte + Expected []byte + BlockSize int + }{ + {[]byte{0x1, 0x2, 0x3, 0x10}, []byte{0x1, 0x2, 0x3, 0x10, 0x4, 0x4, 0x4, 0x4}, 8}, + {[]byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8}, 8}, + } + for _, test := range tests { + padded, err := pad(test.Original, test.BlockSize) + if err != nil { + t.Errorf("pad encountered error: %s", err) + continue + } + if !bytes.Equal(test.Expected, padded) { + t.Errorf("pad results mismatch:\n\tExpected: %X\n\tActual: %X", test.Expected, padded) + } + } +} diff --git a/scep/pkcs7/pkcs7.go b/scep/pkcs7/pkcs7.go new file mode 100644 index 00000000..ccc6cc6d --- /dev/null +++ b/scep/pkcs7/pkcs7.go @@ -0,0 +1,291 @@ +// Package pkcs7 implements parsing and generation of some PKCS#7 structures. +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "sort" + + _ "crypto/sha1" // for crypto.SHA1 +) + +// PKCS7 Represents a PKCS7 structure +type PKCS7 struct { + Content []byte + Certificates []*x509.Certificate + CRLs []pkix.CertificateList + Signers []signerInfo + raw interface{} +} + +type contentInfo struct { + ContentType asn1.ObjectIdentifier + Content asn1.RawValue `asn1:"explicit,optional,tag:0"` +} + +// ErrUnsupportedContentType is returned when a PKCS7 content is not supported. +// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2), +// and Enveloped Data are supported (1.2.840.113549.1.7.3) +var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type") + +type unsignedData []byte + +var ( + // Signed Data OIDs + OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} + OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} + OIDEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} + OIDEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6} + OIDAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} + OIDAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} + OIDAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} + + // Digest Algorithms + OIDDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} + OIDDigestAlgorithmSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + OIDDigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + OIDDigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} + + OIDDigestAlgorithmDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} + OIDDigestAlgorithmDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + + OIDDigestAlgorithmECDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + OIDDigestAlgorithmECDSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + OIDDigestAlgorithmECDSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + OIDDigestAlgorithmECDSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} + + // Signature Algorithms + OIDEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + OIDEncryptionAlgorithmRSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + OIDEncryptionAlgorithmRSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + OIDEncryptionAlgorithmRSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + OIDEncryptionAlgorithmRSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + + OIDEncryptionAlgorithmECDSAP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} + OIDEncryptionAlgorithmECDSAP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} + OIDEncryptionAlgorithmECDSAP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} + + // Encryption Algorithms + OIDEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} + OIDEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} + OIDEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} + OIDEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} + OIDEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} + OIDEncryptionAlgorithmAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46} +) + +func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) { + switch { + case oid.Equal(OIDDigestAlgorithmSHA1), oid.Equal(OIDDigestAlgorithmECDSASHA1), + oid.Equal(OIDDigestAlgorithmDSA), oid.Equal(OIDDigestAlgorithmDSASHA1), + oid.Equal(OIDEncryptionAlgorithmRSA): + return crypto.SHA1, nil + case oid.Equal(OIDDigestAlgorithmSHA256), oid.Equal(OIDDigestAlgorithmECDSASHA256): + return crypto.SHA256, nil + case oid.Equal(OIDDigestAlgorithmSHA384), oid.Equal(OIDDigestAlgorithmECDSASHA384): + return crypto.SHA384, nil + case oid.Equal(OIDDigestAlgorithmSHA512), oid.Equal(OIDDigestAlgorithmECDSASHA512): + return crypto.SHA512, nil + } + return crypto.Hash(0), ErrUnsupportedAlgorithm +} + +// getDigestOIDForSignatureAlgorithm takes an x509.SignatureAlgorithm +// and returns the corresponding OID digest algorithm +func getDigestOIDForSignatureAlgorithm(digestAlg x509.SignatureAlgorithm) (asn1.ObjectIdentifier, error) { + switch digestAlg { + case x509.SHA1WithRSA, x509.ECDSAWithSHA1: + return OIDDigestAlgorithmSHA1, nil + case x509.SHA256WithRSA, x509.ECDSAWithSHA256: + return OIDDigestAlgorithmSHA256, nil + case x509.SHA384WithRSA, x509.ECDSAWithSHA384: + return OIDDigestAlgorithmSHA384, nil + case x509.SHA512WithRSA, x509.ECDSAWithSHA512: + return OIDDigestAlgorithmSHA512, nil + } + return nil, fmt.Errorf("pkcs7: cannot convert hash to oid, unknown hash algorithm") +} + +// getOIDForEncryptionAlgorithm takes the private key type of the signer and +// the OID of a digest algorithm to return the appropriate signerInfo.DigestEncryptionAlgorithm +func getOIDForEncryptionAlgorithm(pkey crypto.PrivateKey, OIDDigestAlg asn1.ObjectIdentifier) (asn1.ObjectIdentifier, error) { + switch pkey.(type) { + case *rsa.PrivateKey: + switch { + default: + return OIDEncryptionAlgorithmRSA, nil + case OIDDigestAlg.Equal(OIDEncryptionAlgorithmRSA): + return OIDEncryptionAlgorithmRSA, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): + return OIDEncryptionAlgorithmRSASHA1, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): + return OIDEncryptionAlgorithmRSASHA256, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): + return OIDEncryptionAlgorithmRSASHA384, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): + return OIDEncryptionAlgorithmRSASHA512, nil + } + case *ecdsa.PrivateKey: + switch { + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): + return OIDDigestAlgorithmECDSASHA1, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): + return OIDDigestAlgorithmECDSASHA256, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): + return OIDDigestAlgorithmECDSASHA384, nil + case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): + return OIDDigestAlgorithmECDSASHA512, nil + } + case *dsa.PrivateKey: + return OIDDigestAlgorithmDSA, nil + } + return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown private key type %T", pkey) + +} + +// Parse decodes a DER encoded PKCS7 package +func Parse(data []byte) (p7 *PKCS7, err error) { + if len(data) == 0 { + return nil, errors.New("pkcs7: input data is empty") + } + var info contentInfo + der, err := ber2der(data) + if err != nil { + return nil, err + } + rest, err := asn1.Unmarshal(der, &info) + if len(rest) > 0 { + err = asn1.SyntaxError{Msg: "trailing data"} + return + } + if err != nil { + return + } + + // fmt.Printf("--> Content Type: %s", info.ContentType) + switch { + case info.ContentType.Equal(OIDSignedData): + return parseSignedData(info.Content.Bytes) + case info.ContentType.Equal(OIDEnvelopedData): + return parseEnvelopedData(info.Content.Bytes) + case info.ContentType.Equal(OIDEncryptedData): + return parseEncryptedData(info.Content.Bytes) + } + return nil, ErrUnsupportedContentType +} + +func parseEnvelopedData(data []byte) (*PKCS7, error) { + var ed envelopedData + if _, err := asn1.Unmarshal(data, &ed); err != nil { + return nil, err + } + return &PKCS7{ + raw: ed, + }, nil +} + +func parseEncryptedData(data []byte) (*PKCS7, error) { + var ed encryptedData + if _, err := asn1.Unmarshal(data, &ed); err != nil { + return nil, err + } + return &PKCS7{ + raw: ed, + }, nil +} + +func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { + if len(raw.Raw) == 0 { + return nil, nil + } + + var val asn1.RawValue + if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { + return nil, err + } + + return x509.ParseCertificates(val.Bytes) +} + +func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { + return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Equal(cert.RawIssuer, ias.IssuerName.FullBytes) +} + +// Attribute represents a key value pair attribute. Value must be marshalable byte +// `encoding/asn1` +type Attribute struct { + Type asn1.ObjectIdentifier + Value interface{} +} + +type attributes struct { + types []asn1.ObjectIdentifier + values []interface{} +} + +// Add adds the attribute, maintaining insertion order +func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { + attrs.types = append(attrs.types, attrType) + attrs.values = append(attrs.values, value) +} + +type sortableAttribute struct { + SortKey []byte + Attribute attribute +} + +type attributeSet []sortableAttribute + +func (sa attributeSet) Len() int { + return len(sa) +} + +func (sa attributeSet) Less(i, j int) bool { + return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 +} + +func (sa attributeSet) Swap(i, j int) { + sa[i], sa[j] = sa[j], sa[i] +} + +func (sa attributeSet) Attributes() []attribute { + attrs := make([]attribute, len(sa)) + for i, attr := range sa { + attrs[i] = attr.Attribute + } + return attrs +} + +func (attrs *attributes) ForMarshalling() ([]attribute, error) { + sortables := make(attributeSet, len(attrs.types)) + for i := range sortables { + attrType := attrs.types[i] + attrValue := attrs.values[i] + asn1Value, err := asn1.Marshal(attrValue) + if err != nil { + return nil, err + } + attr := attribute{ + Type: attrType, + Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag + } + encoded, err := asn1.Marshal(attr) + if err != nil { + return nil, err + } + sortables[i] = sortableAttribute{ + SortKey: encoded, + Attribute: attr, + } + } + sort.Sort(sortables) + return sortables.Attributes(), nil +} diff --git a/scep/pkcs7/pkcs7_test.go b/scep/pkcs7/pkcs7_test.go new file mode 100644 index 00000000..e743192d --- /dev/null +++ b/scep/pkcs7/pkcs7_test.go @@ -0,0 +1,326 @@ +package pkcs7 + +import ( + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" + "os" + "time" +) + +var test1024Key, test2048Key, test3072Key, test4096Key *rsa.PrivateKey + +func init() { + test1024Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("123024078101403810516614073341068864574068590522569345017786163424062310013967742924377390210586226651760719671658568413826602264886073432535341149584680111145880576802262550990305759285883150470245429547886689754596541046564560506544976611114898883158121012232676781340602508151730773214407220733898059285561"), + E: 65537, + }, + D: fromBase10("118892427340746627750435157989073921703209000249285930635312944544706203626114423392257295670807166199489096863209592887347935991101581502404113203993092422730000157893515953622392722273095289787303943046491132467130346663160540744582438810535626328230098940583296878135092036661410664695896115177534496784545"), + Primes: []*big.Int{ + fromBase10("12172745919282672373981903347443034348576729562395784527365032103134165674508405592530417723266847908118361582847315228810176708212888860333051929276459099"), + fromBase10("10106518193772789699356660087736308350857919389391620140340519320928952625438936098550728858345355053201610649202713962702543058578827268756755006576249339"), + }, + } + test1024Key.Precompute() + test2048Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557"), + E: 3, + }, + D: fromBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731"), + Primes: []*big.Int{ + fromBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433"), + fromBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029"), + }, + } + test2048Key.Precompute() + test3072Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("4799422180968749215324244710281712119910779465109490663934897082847293004098645365195947978124390029272750644394844443980065532911010718425428791498896288210928474905407341584968381379157418577471272697781778686372450913810019702928839200328075568223462554606149618941566459398862673532997592879359280754226882565483298027678735544377401276021471356093819491755877827249763065753555051973844057308627201762456191918852016986546071426986328720794061622370410645440235373576002278045257207695462423797272017386006110722769072206022723167102083033531426777518054025826800254337147514768377949097720074878744769255210076910190151785807232805749219196645305822228090875616900385866236956058984170647782567907618713309775105943700661530312800231153745705977436176908325539234432407050398510090070342851489496464612052853185583222422124535243967989533830816012180864309784486694786581956050902756173889941244024888811572094961378021"), + E: 65537, + }, + D: fromBase10("4068124900056380177006532461065648259352178312499768312132802353620854992915205894105621345694615110794369150964768050224096623567443679436821868510233726084582567244003894477723706516831312989564775159596496449435830457803384416702014837685962523313266832032687145914871879794104404800823188153886925022171560391765913739346955738372354826804228989767120353182641396181570533678315099748218734875742705419933837638038793286534641711407564379950728858267828581787483317040753987167237461567332386718574803231955771633274184646232632371006762852623964054645811527580417392163873708539175349637050049959954373319861427407953413018816604365474462455009323937599275324390953644555294418021286807661559165324810415569396577697316798600308544755741549699523972971375304826663847015905713096287495342701286542193782001358775773848824496321550110946106870685499577993864871847542645561943034990484973293461948058147956373115641615329"), + Primes: []*big.Int{ + fromBase10("2378529069722721185825622840841310902793949682948530343491428052737890236476884657507685118578733560141370511507721598189068683665232991988491561624429938984370132428230072355214627085652359350722926394699707232921674771664421591347888367477300909202851476404132163673865768760147403525700174918450753162242834161458300343282159799476695001920226357456953682236859505243928716782707623075239350380352265954107362618991716602898266999700316937680986690964564264877"), + fromBase10("2017811025336026464312837780072272578817919741496395062543647660689775637351085991504709917848745137013798005682591633910555599626950744674459976829106750083386168859581016361317479081273480343110649405858059581933773354781034946787147300862495438979895430001323443224335618577322449133208754541656374335100929456885995320929464029817626916719434010943205170760536768893924932021302887114400922813817969176636993508191950649313115712159241971065134077636674146073"), + }, + } + test3072Key.Precompute() + test4096Key = &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: fromBase10("633335480064287130853997429184971616419051348693342219741748040433588285601270210251206421401040394238592139790962887290698043839174341843721930134010306454716566698330215646704263665452264344664385995704186692432827662862845900348526672531755932642433662686500295989783595767573119607065791980381547677840410600100715146047382485989885183858757974681241303484641390718944520330953604501686666386926996348457928415093305041429178744778762826377713889019740060910363468343855830206640274442887621960581569183233822878661711798998132931623726434336448716605363514220760343097572198620479297583609779817750646169845195672483600293522186340560792255595411601450766002877850696008003794520089358819042318331840490155176019070646738739580486357084733208876620846449161909966690602374519398451042362690200166144326179405976024265116931974936425064291406950542193873313447617169603706868220189295654943247311295475722243471700112334609817776430552541319671117235957754556272646031356496763094955985615723596562217985372503002989591679252640940571608314743271809251568670314461039035793703429977801961867815257832671786542212589906513979094156334941265621017752516999186481477500481433634914622735206243841674973785078408289183000133399026553"), + E: 65537, + }, + D: fromBase10("439373650557744155078930178606343279553665694488479749802070836418412881168612407941793966086633543867614175621952769177088930851151267623886678906158545451731745754402575409204816390946376103491325109185445659065122640946673660760274557781540431107937331701243915001777636528502669576801704352961341634812275635811512806966908648671988644114352046582195051714797831307925775689566757438907578527366568747104508496278929566712224252103563340770696548181508180254674236716995730292431858611476396845443056967589437890065663497768422598977743046882539288481002449571403783500529740184608873520856954837631427724158592309018382711485601884461168736465751756282510065053161144027097169985941910909130083273691945578478173708396726266170473745329617793866669307716920992380350270584929908460462802627239204245339385636926433446418108504614031393494119344916828744888432279343816084433424594432427362258172264834429525166677273382617457205387388293888430391895615438030066428745187333897518037597413369705720436392869403948934993623418405908467147848576977008003556716087129242155836114780890054057743164411952731290520995017097151300091841286806603044227906213832083363876549637037625314539090155417589796428888619937329669464810549362433"), + Primes: []*big.Int{ + fromBase10("25745433817240673759910623230144796182285844101796353869339294232644316274580053211056707671663014355388701931204078502829809738396303142990312095225333440050808647355535878394534263839500592870406002873182360027755750148248672968563366185348499498613479490545488025779331426515670185366021612402246813511722553210128074701620113404560399242413747318161403908617342170447610792422053460359960010544593668037305465806912471260799852789913123044326555978680190904164976511331681163576833618899773550873682147782263100803907156362439021929408298804955194748640633152519828940133338948391986823456836070708197320166146761"), + fromBase10("24599914864909676687852658457515103765368967514652318497893275892114442089314173678877914038802355565271545910572804267918959612739009937926962653912943833939518967731764560204997062096919833970670512726396663920955497151415639902788974842698619579886297871162402643104696160155894685518587660015182381685605752989716946154299190561137541792784125356553411300817844325739404126956793095254412123887617931225840421856505925283322918693259047428656823141903489964287619982295891439430302405252447010728112098326033634688757933930065610737780413018498561434074501822951716586796047404555397992425143397497639322075233073"), + }, + } + test4096Key.Precompute() +} + +func fromBase10(base10 string) *big.Int { + i, ok := new(big.Int).SetString(base10, 10) + if !ok { + panic("bad number: " + base10) + } + return i +} + +type certKeyPair struct { + Certificate *x509.Certificate + PrivateKey *crypto.PrivateKey +} + +func createTestCertificate(sigAlg x509.SignatureAlgorithm) (certKeyPair, error) { + signer, err := createTestCertificateByIssuer("Eddard Stark", nil, sigAlg, true) + if err != nil { + return certKeyPair{}, err + } + pair, err := createTestCertificateByIssuer("Jon Snow", signer, sigAlg, false) + if err != nil { + return certKeyPair{}, err + } + return *pair, nil +} + +func createTestCertificateByIssuer(name string, issuer *certKeyPair, sigAlg x509.SignatureAlgorithm, isCA bool) (*certKeyPair, error) { + var ( + err error + priv crypto.PrivateKey + derCert []byte + issuerCert *x509.Certificate + issuerKey crypto.PrivateKey + ) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, err + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: name, + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now().Add(-1 *time.Second), + NotAfter: time.Now().AddDate(1, 0, 0), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, + } + if issuer != nil { + issuerCert = issuer.Certificate + issuerKey = *issuer.PrivateKey + } + switch sigAlg { + case x509.SHA1WithRSA: + priv = test1024Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + case x509.SHA256WithRSA: + priv = test2048Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA256WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA256 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.SHA384WithRSA: + priv = test3072Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA384WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA384 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.SHA512WithRSA: + priv = test4096Key + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA512WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA512 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA1: + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + case x509.ECDSAWithSHA256: + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA256WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA256 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA384: + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA384WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA384 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.ECDSAWithSHA512: + priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA512WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA512 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA256 + } + case x509.DSAWithSHA1: + var dsaPriv dsa.PrivateKey + params := &dsaPriv.Parameters + err = dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160) + if err != nil { + return nil, err + } + err = dsa.GenerateKey(&dsaPriv, rand.Reader) + if err != nil { + return nil, err + } + switch issuerKey.(type) { + case *rsa.PrivateKey: + template.SignatureAlgorithm = x509.SHA1WithRSA + case *ecdsa.PrivateKey: + template.SignatureAlgorithm = x509.ECDSAWithSHA1 + case *dsa.PrivateKey: + template.SignatureAlgorithm = x509.DSAWithSHA1 + } + priv = &dsaPriv + } + if isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + template.BasicConstraintsValid = true + } + if issuer == nil { + // no issuer given,make this a self-signed root cert + issuerCert = &template + issuerKey = priv + } + + log.Println("creating cert", name, "issued by", issuerCert.Subject.CommonName, "with sigalg", sigAlg) + switch priv.(type) { + case *rsa.PrivateKey: + switch issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) + } + case *ecdsa.PrivateKey: + switch issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) + } + case *dsa.PrivateKey: + pub := &priv.(*dsa.PrivateKey).PublicKey + switch issuerKey := issuerKey.(type) { + case *rsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, pub, issuerKey) + case *ecdsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) + case *dsa.PrivateKey: + derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) + } + } + if err != nil { + return nil, err + } + if len(derCert) == 0 { + return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) + } + cert, err := x509.ParseCertificate(derCert) + if err != nil { + return nil, err + } + pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + return &certKeyPair{ + Certificate: cert, + PrivateKey: &priv, + }, nil +} + +type TestFixture struct { + Input []byte + Certificate *x509.Certificate + PrivateKey *rsa.PrivateKey +} + +func UnmarshalTestFixture(testPEMBlock string) TestFixture { + var result TestFixture + var derBlock *pem.Block + var pemBlock = []byte(testPEMBlock) + for { + derBlock, pemBlock = pem.Decode(pemBlock) + if derBlock == nil { + break + } + switch derBlock.Type { + case "PKCS7": + result.Input = derBlock.Bytes + case "CERTIFICATE": + result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes) + case "PRIVATE KEY": + result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes) + } + } + + return result +} diff --git a/scep/pkcs7/sign.go b/scep/pkcs7/sign.go new file mode 100644 index 00000000..addd7638 --- /dev/null +++ b/scep/pkcs7/sign.go @@ -0,0 +1,429 @@ +package pkcs7 + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "math/big" + "time" +) + +// SignedData is an opaque data structure for creating signed data payloads +type SignedData struct { + sd signedData + certs []*x509.Certificate + data, messageDigest []byte + digestOid asn1.ObjectIdentifier + encryptionOid asn1.ObjectIdentifier +} + +// NewSignedData takes data and initializes a PKCS7 SignedData struct that is +// ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default +// and can be changed by calling SetDigestAlgorithm. +func NewSignedData(data []byte) (*SignedData, error) { + content, err := asn1.Marshal(data) + if err != nil { + return nil, err + } + ci := contentInfo{ + ContentType: OIDData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + sd := signedData{ + ContentInfo: ci, + Version: 1, + } + return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1}, nil +} + +// SignerInfoConfig are optional values to include when adding a signer +type SignerInfoConfig struct { + ExtraSignedAttributes []Attribute + ExtraUnsignedAttributes []Attribute +} + +type signedData struct { + Version int `asn1:"default:1"` + DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` + ContentInfo contentInfo + Certificates rawCertificates `asn1:"optional,tag:0"` + CRLs []pkix.CertificateList `asn1:"optional,tag:1"` + SignerInfos []signerInfo `asn1:"set"` +} + +type signerInfo struct { + Version int `asn1:"default:1"` + IssuerAndSerialNumber issuerAndSerial + DigestAlgorithm pkix.AlgorithmIdentifier + AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"` + DigestEncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedDigest []byte + UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"` +} + +type attribute struct { + Type asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"set"` +} + +func marshalAttributes(attrs []attribute) ([]byte, error) { + encodedAttributes, err := asn1.Marshal(struct { + A []attribute `asn1:"set"` + }{A: attrs}) + if err != nil { + return nil, err + } + + // Remove the leading sequence octets + var raw asn1.RawValue + asn1.Unmarshal(encodedAttributes, &raw) + return raw.Bytes, nil +} + +type rawCertificates struct { + Raw asn1.RawContent +} + +type issuerAndSerial struct { + IssuerName asn1.RawValue + SerialNumber *big.Int +} + +// SetDigestAlgorithm sets the digest algorithm to be used in the signing process. +// +// This should be called before adding signers +func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) { + sd.digestOid = d +} + +// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process. +// +// This should be called before adding signers +func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) { + sd.encryptionOid = d +} + +// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent. +func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { + var parents []*x509.Certificate + return sd.AddSignerChain(ee, pkey, parents, config) +} + +// AddSignerChain signs attributes about the content and adds certificates +// and signers infos to the Signed Data. The certificate and private key +// of the end-entity signer are used to issue the signature, and any +// parent of that end-entity that need to be added to the list of +// certifications can be specified in the parents slice. +// +// The signature algorithm used to hash the data is the one of the end-entity +// certificate. +func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error { +// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of +// the issuer of the end-entity signer is stored in the issuerAndSerialNumber +// section of the SignedData.SignerInfo, alongside the serial number of +// the end-entity. + var ias issuerAndSerial + ias.SerialNumber = ee.SerialNumber + if len(parents) == 0 { + // no parent, the issuer is the end-entity cert itself + ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} + } else { + err := verifyPartialChain(ee, parents) + if err != nil { + return err + } + // the first parent is the issuer + ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject} + } + sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, + pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + ) + hash, err := getHashForOID(sd.digestOid) + if err != nil { + return err + } + h := hash.New() + h.Write(sd.data) + sd.messageDigest = h.Sum(nil) + encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) + if err != nil { + return err + } + attrs := &attributes{} + attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) + attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) + attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) + for _, attr := range config.ExtraSignedAttributes { + attrs.Add(attr.Type, attr.Value) + } + finalAttrs, err := attrs.ForMarshalling() + if err != nil { + return err + } + unsignedAttrs := &attributes{} + for _, attr := range config.ExtraUnsignedAttributes { + unsignedAttrs.Add(attr.Type, attr.Value) + } + finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() + if err != nil { + return err + } + // create signature of signed attributes + signature, err := signAttributes(finalAttrs, pkey, hash) + if err != nil { + return err + } + signer := signerInfo{ + AuthenticatedAttributes: finalAttrs, + UnauthenticatedAttributes: finalUnsignedAttrs, + DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid}, + IssuerAndSerialNumber: ias, + EncryptedDigest: signature, + Version: 1, + } + sd.certs = append(sd.certs, ee) + if len(parents) > 0 { + sd.certs = append(sd.certs, parents...) + } + sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) + return nil +} + +// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData. +// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone +// and does not include any signed attributes like timestamp and so on. +// +// This function is needed to sign old Android APKs, something you probably +// shouldn't do unless you're maintaining backward compatibility for old +// applications. +func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { + var signature []byte + sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}) + hash, err := getHashForOID(sd.digestOid) + if err != nil { + return err + } + h := hash.New() + h.Write(sd.data) + sd.messageDigest = h.Sum(nil) + switch pkey := pkey.(type) { + case *dsa.PrivateKey: + // dsa doesn't implement crypto.Signer so we make a special case + // https://github.com/golang/go/issues/27889 + r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest) + if err != nil { + return err + } + signature, err = asn1.Marshal(dsaSignature{r, s}) + if err != nil { + return err + } + default: + key, ok := pkey.(crypto.Signer) + if !ok { + return errors.New("pkcs7: private key does not implement crypto.Signer") + } + signature, err = key.Sign(rand.Reader, sd.messageDigest, hash) + if err != nil { + return err + } + } + var ias issuerAndSerial + ias.SerialNumber = ee.SerialNumber + // no parent, the issue is the end-entity cert itself + ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} + if sd.encryptionOid == nil { + // if the encryption algorithm wasn't set by SetEncryptionAlgorithm, + // infer it from the digest algorithm + sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid) + } + if err != nil { + return err + } + signer := signerInfo{ + DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, + DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid}, + IssuerAndSerialNumber: ias, + EncryptedDigest: signature, + Version: 1, + } + // create signature of signed attributes + sd.certs = append(sd.certs, ee) + sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) + return nil +} + +func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error { + unsignedAttrs := &attributes{} + for _, attr := range extraUnsignedAttrs { + unsignedAttrs.Add(attr.Type, attr.Value) + } + finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() + if err != nil { + return err + } + + si.UnauthenticatedAttributes = finalUnsignedAttrs + + return nil +} + +// AddCertificate adds the certificate to the payload. Useful for parent certificates +func (sd *SignedData) AddCertificate(cert *x509.Certificate) { + sd.certs = append(sd.certs, cert) +} + +// Detach removes content from the signed data struct to make it a detached signature. +// This must be called right before Finish() +func (sd *SignedData) Detach() { + sd.sd.ContentInfo = contentInfo{ContentType: OIDData} +} + +// GetSignedData returns the private Signed Data +func (sd *SignedData) GetSignedData() *signedData { + return &sd.sd +} + +// Finish marshals the content and its signers +func (sd *SignedData) Finish() ([]byte, error) { + sd.sd.Certificates = marshalCertificates(sd.certs) + inner, err := asn1.Marshal(sd.sd) + if err != nil { + return nil, err + } + outer := contentInfo{ + ContentType: OIDSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, + } + return asn1.Marshal(outer) +} + +// RemoveAuthenticatedAttributes removes authenticated attributes from signedData +// similar to OpenSSL's PKCS7_NOATTR or -noattr flags +func (sd *SignedData) RemoveAuthenticatedAttributes() { + for i := range sd.sd.SignerInfos { + sd.sd.SignerInfos[i].AuthenticatedAttributes = nil + } +} + +// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData +func (sd *SignedData) RemoveUnauthenticatedAttributes() { + for i := range sd.sd.SignerInfos { + sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil + } +} + +// verifyPartialChain checks that a given cert is issued by the first parent in the list, +// then continue down the path. It doesn't require the last parent to be a root CA, +// or to be trusted in any truststore. It simply verifies that the chain provided, albeit +// partial, makes sense. +func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error { + if len(parents) == 0 { + return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName) + } + err := cert.CheckSignatureFrom(parents[0]) + if err != nil { + return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err) + } + if len(parents) == 1 { + // there is no more parent to check, return + return nil + } + return verifyPartialChain(parents[0], parents[1:]) +} + +func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { + var ias issuerAndSerial + // The issuer RDNSequence has to match exactly the sequence in the certificate + // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence + ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} + ias.SerialNumber = cert.SerialNumber + + return ias, nil +} + +// signs the DER encoded form of the attributes with the private key +func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) { + attrBytes, err := marshalAttributes(attrs) + if err != nil { + return nil, err + } + h := digestAlg.New() + h.Write(attrBytes) + hash := h.Sum(nil) + + // dsa doesn't implement crypto.Signer so we make a special case + // https://github.com/golang/go/issues/27889 + switch pkey := pkey.(type) { + case *dsa.PrivateKey: + r, s, err := dsa.Sign(rand.Reader, pkey, hash) + if err != nil { + return nil, err + } + return asn1.Marshal(dsaSignature{r, s}) + } + + key, ok := pkey.(crypto.Signer) + if !ok { + return nil, errors.New("pkcs7: private key does not implement crypto.Signer") + } + return key.Sign(rand.Reader, hash, digestAlg) +} + +type dsaSignature struct { + R, S *big.Int +} + +// concats and wraps the certificates in the RawValue structure +func marshalCertificates(certs []*x509.Certificate) rawCertificates { + var buf bytes.Buffer + for _, cert := range certs { + buf.Write(cert.Raw) + } + rawCerts, _ := marshalCertificateBytes(buf.Bytes()) + return rawCerts +} + +// Even though, the tag & length are stripped out during marshalling the +// RawContent, we have to encode it into the RawContent. If its missing, +// then `asn1.Marshal()` will strip out the certificate wrapper instead. +func marshalCertificateBytes(certs []byte) (rawCertificates, error) { + var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} + b, err := asn1.Marshal(val) + if err != nil { + return rawCertificates{}, err + } + return rawCertificates{Raw: b}, nil +} + +// DegenerateCertificate creates a signed data structure containing only the +// provided certificate or certificate chain. +func DegenerateCertificate(cert []byte) ([]byte, error) { + rawCert, err := marshalCertificateBytes(cert) + if err != nil { + return nil, err + } + emptyContent := contentInfo{ContentType: OIDData} + sd := signedData{ + Version: 1, + ContentInfo: emptyContent, + Certificates: rawCert, + CRLs: []pkix.CertificateList{}, + } + content, err := asn1.Marshal(sd) + if err != nil { + return nil, err + } + signedContent := contentInfo{ + ContentType: OIDSignedData, + Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, + } + return asn1.Marshal(signedContent) +} diff --git a/scep/pkcs7/sign_test.go b/scep/pkcs7/sign_test.go new file mode 100644 index 00000000..0ba6324d --- /dev/null +++ b/scep/pkcs7/sign_test.go @@ -0,0 +1,266 @@ +package pkcs7 + +import ( + "bytes" + "crypto/dsa" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "math/big" + "os" + "os/exec" + "testing" +) + +func TestSign(t *testing.T) { + content := []byte("Hello World") + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + x509.ECDSAWithSHA1, + x509.ECDSAWithSHA256, + x509.ECDSAWithSHA384, + x509.ECDSAWithSHA512, + } + for _, sigalgroot := range sigalgs { + rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) + if err != nil { + t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) + } + truststore := x509.NewCertPool() + truststore.AddCert(rootCert.Certificate) + for _, sigalginter := range sigalgs { + interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) + if err != nil { + t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) + } + var parents []*x509.Certificate + parents = append(parents, interCert.Certificate) + for _, sigalgsigner := range sigalgs { + signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + for _, testDetach := range []bool{false, true} { + log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach) + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + + // Set the digest to match the end entity cert + signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm) + toBeSigned.SetDigestAlgorithm(signerDigest) + + if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil { + t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if testDetach { + toBeSigned.Detach() + } + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) + p7, err := Parse(signed) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if testDetach { + p7.Content = content + } + if !bytes.Equal(content, p7.Content) { + t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content) + } + if err := p7.VerifyWithChain(truststore); err != nil { + t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) { + t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q", + sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm) + } + } + } + } + } +} + +func TestDSASignAndVerifyWithOpenSSL(t *testing.T) { + content := []byte("Hello World") + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + + block, _ := pem.Decode([]byte(dsaPublicCert)) + if block == nil { + t.Fatal("failed to parse certificate PEM") + } + signerCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatal("failed to parse certificate: " + err.Error()) + } + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signer") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) + + priv := dsa.PrivateKey{ + PublicKey: dsa.PublicKey{Parameters: dsa.Parameters{P: fromHex("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7"), + Q: fromHex("9760508F15230BCCB292B982A2EB840BF0581CF5"), + G: fromHex("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A"), + }, + }, + X: fromHex("7D6E1A3DD4019FD809669D8AB8DA73807CEF7EC1"), + } + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("test case: cannot initialize signed data: %s", err) + } + if err := toBeSigned.SignWithoutAttr(signerCert, &priv, SignerInfoConfig{}); err != nil { + t.Fatalf("Cannot add signer: %s", err) + } + toBeSigned.Detach() + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("test case: cannot finish signing data: %s", err) + } + + // write the signature to a temp file + tmpSignatureFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signature") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignatureFile.Name(), pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: signed}), 0755) + + // call openssl to verify the signature on the content using the root + opensslCMD := exec.Command("openssl", "smime", "-verify", "-noverify", + "-in", tmpSignatureFile.Name(), "-inform", "PEM", + "-content", tmpContentFile.Name()) + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("test case: openssl command failed with %s: %s", err, out) + } + os.Remove(tmpSignatureFile.Name()) // clean up + os.Remove(tmpContentFile.Name()) // clean up + os.Remove(tmpSignerCertFile.Name()) // clean up +} + +func ExampleSignedData() { + // generate a signing cert or load a key pair + cert, err := createTestCertificate(x509.SHA256WithRSA) + if err != nil { + fmt.Printf("Cannot create test certificates: %s", err) + } + + // Initialize a SignedData struct with content to be signed + signedData, err := NewSignedData([]byte("Example data to be signed")) + if err != nil { + fmt.Printf("Cannot initialize signed data: %s", err) + } + + // Add the signing cert and private key + if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil { + fmt.Printf("Cannot add signer: %s", err) + } + + // Call Detach() is you want to remove content from the signature + // and generate an S/MIME detached signature + signedData.Detach() + + // Finish() to obtain the signature bytes + detachedSignature, err := signedData.Finish() + if err != nil { + fmt.Printf("Cannot finish signing data: %s", err) + } + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature}) +} + +func TestUnmarshalSignedAttribute(t *testing.T) { + cert, err := createTestCertificate(x509.SHA512WithRSA) + if err != nil { + t.Fatal(err) + } + content := []byte("Hello World") + toBeSigned, err := NewSignedData(content) + if err != nil { + t.Fatalf("Cannot initialize signed data: %s", err) + } + oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7} + testValue := "TestValue" + if err := toBeSigned.AddSigner(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{ + ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}}, + }); err != nil { + t.Fatalf("Cannot add signer: %s", err) + } + signed, err := toBeSigned.Finish() + if err != nil { + t.Fatalf("Cannot finish signing data: %s", err) + } + p7, err := Parse(signed) + if err != nil { + t.Fatalf("Cannot parse signed data: %v", err) + } + var actual string + err = p7.UnmarshalSignedAttribute(oidTest, &actual) + if err != nil { + t.Fatalf("Cannot unmarshal test value: %s", err) + } + if testValue != actual { + t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual) + } +} + +func TestDegenerateCertificate(t *testing.T) { + cert, err := createTestCertificate(x509.SHA1WithRSA) + if err != nil { + t.Fatal(err) + } + deg, err := DegenerateCertificate(cert.Certificate.Raw) + if err != nil { + t.Fatal(err) + } + testOpenSSLParse(t, deg) + pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg}) +} + +// writes the cert to a temporary file and tests that openssl can read it. +func testOpenSSLParse(t *testing.T, certBytes []byte) { + tmpCertFile, err := ioutil.TempFile("", "testCertificate") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpCertFile.Name()) // clean up + + if _, err := tmpCertFile.Write(certBytes); err != nil { + t.Fatal(err) + } + + opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name()) + _, err = opensslCMD.Output() + if err != nil { + t.Fatal(err) + } + + if err := tmpCertFile.Close(); err != nil { + t.Fatal(err) + } + +} +func fromHex(s string) *big.Int { + result, ok := new(big.Int).SetString(s, 16) + if !ok { + panic(s) + } + return result +} diff --git a/scep/pkcs7/verify.go b/scep/pkcs7/verify.go new file mode 100644 index 00000000..c8ead236 --- /dev/null +++ b/scep/pkcs7/verify.go @@ -0,0 +1,264 @@ +package pkcs7 + +import ( + "crypto/subtle" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "time" +) + +// Verify is a wrapper around VerifyWithChain() that initializes an empty +// trust store, effectively disabling certificate verification when validating +// a signature. +func (p7 *PKCS7) Verify() (err error) { + return p7.VerifyWithChain(nil) +} + +// VerifyWithChain checks the signatures of a PKCS7 object. +// If truststore is not nil, it also verifies the chain of trust of the end-entity +// signer cert to one of the root in the truststore. +func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) { + if len(p7.Signers) == 0 { + return errors.New("pkcs7: Message has no signers") + } + for _, signer := range p7.Signers { + if err := verifySignature(p7, signer, truststore); err != nil { + return err + } + } + return nil +} + +func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) { + signedData := p7.Content + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) + if ee == nil { + return errors.New("pkcs7: No certificate for signer") + } + signingTime := time.Now().UTC() + if len(signer.AuthenticatedAttributes) > 0 { + // TODO(fullsailor): First check the content type match + var digest []byte + err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) + if err != nil { + return err + } + hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) + if err != nil { + return err + } + h := hash.New() + h.Write(p7.Content) + computed := h.Sum(nil) + if subtle.ConstantTimeCompare(digest, computed) != 1 { + return &MessageDigestMismatchError{ + ExpectedDigest: digest, + ActualDigest: computed, + } + } + signedData, err = marshalAttributes(signer.AuthenticatedAttributes) + if err != nil { + return err + } + err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) + if err == nil { + // signing time found, performing validity check + if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { + return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", + signingTime.Format(time.RFC3339), + ee.NotBefore.Format(time.RFC3339), + ee.NotBefore.Format(time.RFC3339)) + } + } + } + if truststore != nil { + _, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime) + if err != nil { + return err + } + } + sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) + if err != nil { + return err + } + return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) +} + +// GetOnlySigner returns an x509.Certificate for the first signer of the signed +// data payload. If there are more or less than one signer, nil is returned +func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { + if len(p7.Signers) != 1 { + return nil + } + signer := p7.Signers[0] + return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) +} + +// UnmarshalSignedAttribute decodes a single attribute from the signer info +func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { + sd, ok := p7.raw.(signedData) + if !ok { + return errors.New("pkcs7: payload is not signedData content") + } + if len(sd.SignerInfos) < 1 { + return errors.New("pkcs7: payload has no signers") + } + attributes := sd.SignerInfos[0].AuthenticatedAttributes + return unmarshalAttribute(attributes, attributeType, out) +} + +func parseSignedData(data []byte) (*PKCS7, error) { + var sd signedData + asn1.Unmarshal(data, &sd) + certs, err := sd.Certificates.Parse() + if err != nil { + return nil, err + } + // fmt.Printf("--> Signed Data Version %d\n", sd.Version) + + var compound asn1.RawValue + var content unsignedData + + // The Content.Bytes maybe empty on PKI responses. + if len(sd.ContentInfo.Content.Bytes) > 0 { + if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { + return nil, err + } + } + // Compound octet string + if compound.IsCompound { + if compound.Tag == 4 { + if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { + return nil, err + } + } else { + content = compound.Bytes + } + } else { + // assuming this is tag 04 + content = compound.Bytes + } + return &PKCS7{ + Content: content, + Certificates: certs, + CRLs: sd.CRLs, + Signers: sd.SignerInfos, + raw: sd}, nil +} + +// verifyCertChain takes an end-entity certs, a list of potential intermediates and a +// truststore, and built all potential chains between the EE and a trusted root. +// +// When verifying chains that may have expired, currentTime can be set to a past date +// to allow the verification to pass. If unset, currentTime is set to the current UTC time. +func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) { + intermediates := x509.NewCertPool() + for _, intermediate := range certs { + intermediates.AddCert(intermediate) + } + verifyOptions := x509.VerifyOptions{ + Roots: truststore, + Intermediates: intermediates, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + CurrentTime: currentTime, + } + chains, err = ee.Verify(verifyOptions) + if err != nil { + return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err) + } + return +} + +// MessageDigestMismatchError is returned when the signer data digest does not +// match the computed digest for the contained content +type MessageDigestMismatchError struct { + ExpectedDigest []byte + ActualDigest []byte +} + +func (err *MessageDigestMismatchError) Error() string { + return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) +} + +func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) { + switch { + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1): + return x509.ECDSAWithSHA1, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256): + return x509.ECDSAWithSHA256, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384): + return x509.ECDSAWithSHA384, nil + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512): + return x509.ECDSAWithSHA512, nil + case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.SHA1WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.SHA256WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): + return x509.SHA384WithRSA, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): + return x509.SHA512WithRSA, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA), + digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.DSAWithSHA1, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.DSAWithSHA256, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384), + digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521): + switch { + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): + return x509.ECDSAWithSHA1, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): + return x509.ECDSAWithSHA256, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): + return x509.ECDSAWithSHA384, nil + case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): + return x509.ECDSAWithSHA512, nil + default: + return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", + digest.Algorithm.String(), digestEncryption.Algorithm.String()) + } + default: + return -1, fmt.Errorf("pkcs7: unsupported algorithm %q", + digestEncryption.Algorithm.String()) + } +} + +func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { + for _, cert := range certs { + if isCertMatchForIssuerAndSerial(cert, ias) { + return cert + } + } + return nil +} + +func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { + for _, attr := range attrs { + if attr.Type.Equal(attributeType) { + _, err := asn1.Unmarshal(attr.Value.Bytes, out) + return err + } + } + return errors.New("pkcs7: attribute type not in attributes") +} diff --git a/scep/pkcs7/verify_test.go b/scep/pkcs7/verify_test.go new file mode 100644 index 00000000..f80943b2 --- /dev/null +++ b/scep/pkcs7/verify_test.go @@ -0,0 +1,713 @@ +package pkcs7 + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "os/exec" + "testing" + "time" +) + +func TestVerify(t *testing.T) { + fixture := UnmarshalTestFixture(SignedTestFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } + expected := []byte("We the People") + if !bytes.Equal(p7.Content, expected) { + t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content) + + } +} + +var SignedTestFixture = ` +-----BEGIN PKCS7----- +MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B +BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG +SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh +cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB +Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP +J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF +a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw +DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8 +zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+ +L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy +axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD +bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI +hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4 +LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG +SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX +iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM +FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt +ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2 +MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu +b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0 +bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F +1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5 +SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG +9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9 +8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR +3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw +TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih +Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB +AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY +5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI +1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14 +qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB +Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J ++t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ +4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg +ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF +JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3 +BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g== +-----END PRIVATE KEY-----` + +func TestVerifyEC2(t *testing.T) { + fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Certificates = []*x509.Certificate{fixture.Certificate} + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var EC2IdentityDocumentFixture = ` +-----BEGIN PKCS7----- +MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA +JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh +eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1 +cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh +bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs +bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg +OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK +ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj +aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy +YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA +AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n +dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi +IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B +CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG +CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w +LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA +AAAAAA== +-----END PKCS7----- +-----BEGIN CERTIFICATE----- +MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw +FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD +VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z +ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u +IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl +cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e +ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 +VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P +hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j +k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U +hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF +lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf +MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW +MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw +vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw +7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K +-----END CERTIFICATE-----` + +func TestVerifyAppStore(t *testing.T) { + fixture := UnmarshalTestFixture(AppStoreReceiptFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var AppStoreReceiptFixture = ` +-----BEGIN PKCS7----- +MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI +hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC +AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL +AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE +AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy +NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz +gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh +YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx +WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a +8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m +MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91 +Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE +AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz +AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM +AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB +AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw +MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt +MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa +MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD +AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR +BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl +bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv +cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw +MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl +IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs +ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg +SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid +xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3 +k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ +uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb +XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI +HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw +LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0 +MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G +A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw +ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u +IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j +ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k +aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0 +aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3 +LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA +MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH +bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut +F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg +BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz +p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o +bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF +MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL +MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB +MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT +MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg +RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl +dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0 +U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV +CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8 +V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl +d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q +arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj +gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF +MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw +JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/ +BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z +viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N +w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ +TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V +AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur ++cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR +pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw +CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew +HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET +MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne ++Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz +y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ +Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS +C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB +hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB +djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp +R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/ +CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC +ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB +thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz +c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk +IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5 +IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3 +DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU +sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ +fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr +1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk +wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq +xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX +b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y +bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide +NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa +ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv +MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD +VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/ +QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD +4B85NkrgvQsWAQ== +-----END PKCS7-----` + +func TestVerifyApkEcdsa(t *testing.T) { + fixture := UnmarshalTestFixture(ApkEcdsaFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Content, err = base64.StdEncoding.DecodeString(ApkEcdsaContent) + if err != nil { + t.Errorf("Failed to decode base64 signature file: %v", err) + } + if err := p7.Verify(); err != nil { + t.Errorf("Verify failed with error: %v", err) + } +} + +var ApkEcdsaFixture = `-----BEGIN PKCS7----- +MIIDAgYJKoZIhvcNAQcCoIIC8zCCAu8CAQExDzANBglghkgBZQMEAgMFADALBgkq +hkiG9w0BBwGgggH3MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAME +MBIxEDAOBgNVBAMMB2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUz +MTIyWjASMRAwDgYDVQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG +AAQAYX95sSjPEQqgyLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9G +hAyIyQSnNrIg0zDlWQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCP +B0p+fEXDbq8QFDYvlh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1Ud +DgQWBBT/Ra3kz60gQ7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tY +k3byZckcLabt8TAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39 +hYLsWk2H84oEw+HJqGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcD +j2VfNCHazuXO3kSxGA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJV +A/2uEoeqvnl7BsdkicyaOBNEADijuVdaPPIWzKClt9OaVxExgdAwgc0CAQEwHzAS +MRAwDgYDVQQDDAdlYy1wNTIxAgkA7Fd0Wy+bdiIwDQYJYIZIAWUDBAIDBQAwCgYI +KoZIzj0EAwQEgYswgYgCQgD1pVSNo7qTm9A6tpt3SU2yRa+xpJAnUbpZ+Gu36B71 +JnQBUzRgTGevniqHpyagi7b2zjWh1uvfz9FfrITUwGMddgJCAPjiBRcl7rKpxmZn +V1MvcJOX41xRSJu1wmBiYXqaJarL+gQ/Wl7RYsMtqLjmNColvLaHNxCaWOO/8nAE +Hg0OMA60 +-----END PKCS7-----` + +var ApkEcdsaContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KU0hBLTUxMi1EaWdlc3QtTWFuaWZlc3Q6IFAvVDRqSWtTMjQvNzFxeFE2WW1MeEtNdkRPUUF0WjUxR090dFRzUU9yemhHRQ0KIEpaUGVpWUtyUzZYY090bStYaWlFVC9uS2tYdWVtUVBwZ2RBRzFKUzFnPT0NCkNyZWF0ZWQtQnk6IDEuMCAoQW5kcm9pZCBTaWduQXBrKQ0KDQpOYW1lOiBBbmRyb2lkTWFuaWZlc3QueG1sDQpTSEEtNTEyLURpZ2VzdDogcm9NbWVQZmllYUNQSjFJK2VzMVpsYis0anB2UXowNHZqRWVpL2U0dkN1ald0VVVWSHEzMkNXDQogMUxsOHZiZGMzMCtRc1FlN29ibld4dmhLdXN2K3c1a2c9PQ0KDQpOYW1lOiByZXNvdXJjZXMuYXJzYw0KU0hBLTUxMi1EaWdlc3Q6IG5aYW1aUzlPZTRBRW41cEZaaCtoQ1JFT3krb1N6a3hHdU5YZU0wUFF6WGVBVlVQV3hSVzFPYQ0KIGVLbThRbXdmTmhhaS9HOEcwRUhIbHZEQWdlcy9HUGtBPT0NCg0KTmFtZTogY2xhc3Nlcy5kZXgNClNIQS01MTItRGlnZXN0OiBlbWlDQld2bkVSb0g2N2lCa3EwcUgrdm5tMkpaZDlMWUNEV051N3RNYzJ3bTRtV0dYSUVpWmcNCiBWZkVPV083MFRlZnFjUVhldkNtN2hQMnRpT0U3Y0w5UT09DQoNCg==` + +func TestVerifyFirefoxAddon(t *testing.T) { + fixture := UnmarshalTestFixture(FirefoxAddonFixture) + p7, err := Parse(fixture.Input) + if err != nil { + t.Errorf("Parse encountered unexpected error: %v", err) + } + p7.Content = FirefoxAddonContent + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(FirefoxAddonRootCert) + if err := p7.VerifyWithChain(certPool); err != nil { + t.Errorf("Verify failed with error: %v", err) + } + // Verify the certificate chain to make sure the identified root + // is the one we expect + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) + if ee == nil { + t.Errorf("No end-entity certificate found for signer") + } + signingTime, _ := time.Parse(time.RFC3339, "2017-02-23 09:06:16-05:00") + chains, err := verifyCertChain(ee, p7.Certificates, certPool, signingTime) + if err != nil { + t.Error(err) + } + if len(chains) != 1 { + t.Errorf("Expected to find one chain, but found %d", len(chains)) + } + if len(chains[0]) != 3 { + t.Errorf("Expected to find three certificates in chain, but found %d", len(chains[0])) + } + if chains[0][0].Subject.CommonName != "tabscope@xuldev.org" { + t.Errorf("Expected to find EE certificate with subject 'tabscope@xuldev.org', but found '%s'", chains[0][0].Subject.CommonName) + } + if chains[0][1].Subject.CommonName != "production-signing-ca.addons.mozilla.org" { + t.Errorf("Expected to find intermediate certificate with subject 'production-signing-ca.addons.mozilla.org', but found '%s'", chains[0][1].Subject.CommonName) + } + if chains[0][2].Subject.CommonName != "root-ca-production-amo" { + t.Errorf("Expected to find root certificate with subject 'root-ca-production-amo', but found '%s'", chains[0][2].Subject.CommonName) + } +} + +var FirefoxAddonContent = []byte(`Signature-Version: 1.0 +MD5-Digest-Manifest: KjRavc6/KNpuT1QLcB/Gsg== +SHA1-Digest-Manifest: 5Md5nUg+U7hQ/UfzV+xGKWOruVI= + +`) + +var FirefoxAddonFixture = ` +-----BEGIN PKCS7----- +MIIQTAYJKoZIhvcNAQcCoIIQPTCCEDkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 +DQEHAaCCDL0wggW6MIIDoqADAgECAgYBVpobWVwwDQYJKoZIhvcNAQELBQAwgcUx +CzAJBgNVBAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYD +VQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8G +A1UEAxMocHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0 +MDIGCSqGSIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxh +LmNvbTAeFw0xNjA4MTcyMDA0NThaFw0yMTA4MTYyMDA0NThaMHYxEzARBgNVBAsT +ClByb2R1Y3Rpb24xCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3 +MQ8wDQYDVQQKEwZBZGRvbnMxCzAJBgNVBAgTAkNBMRwwGgYDVQQDFBN0YWJzY29w +ZUB4dWxkZXYub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv6e0 +mPD8dt4J8HTNNq4ODns2DV6Weh1hllCIFvOeu1u3UrR03st0BMY8OXYwr/NvRVjg +bA8gRySWAL+XqLzbhtXNeNegAoxrF+3mYY5rJjsLj/FGI6P6OXjngqwgm9VTBl7m +jh/KXBSwYoUcavJo6cmk8sCFwoblyQiv+tsWaUCOI6zMzubNtIS+GFvET9y/VZMP +j6mk8O10wBgJF5MMtA19va3qXy7aCZ7DnZp1l3equd/L6t324TtXoqx6xWQKo6TM +I0mcTlKvm6TKegTGBCyGn3JRARoIJv4AW1qqgyaHXf9EoY2pKT8Avkri5++NuSJ6 +jtO4k/diBA2MZU20U0KGffYZNTxKDqd6XtI6y1tJPd/OWRFyU+mHntkcm9sar7L3 +nPKujHRox2re10ec1WBnJE3PjlAoesNjxzp+xs2mGGc8DX9NuWn+1uK9xmgGIIMl +OFfyQ4s0G6hKp5goFcrFZxmexu0ZahOs8vZf8xDBW7yR1zToQElOXHvrscM386os +kOF9IxQZfcCoPuNQVg1haCONNkx0oau3RQQlOSAZtC79b+rBjQ5JYfjRLYAworf2 +xQaprCh33TD1dTBrvzEbCGszgkN53Vqh5TFBjbU/NyldOkGvK8Xf6WhT5u+aftnV +lbuE2McAg6x1AlloUZq6PNTBpz7zypcIISnQ+y8CAwEAATANBgkqhkiG9w0BAQsF +AAOCAgEAIBoo2+OEYNCgP/IbUj9azaf/lde1q4AK/uTMoUeS5WcrXd8aqA0Y1qV7 +xUALgDQAExXgqcOMGu4mPMaoZDgwGI4Tj7XPJQq5Z5zYxpRf/Wtzae33T9BF6QPW +v5xiRYuol+FbEtqRHZqxDWtIrd1MWBy3wjO3pLPdzDM9jWh+HLxdGWThJszaZp3T +CqsOx+l9W0Q7qM5ioZpHStgXDfhw38Lg++kLnzcX9MqsjYyezdwE4krqW6hK3+4S +0LZE4dTgsy8JULkyAF3HrPWEXESnD7c4mx6owZe+BNDK5hsVM/obAqH7sJq/igbM +5N1l832p/ws8l5xKOr3qBWSzWn6u7ExvqG6Ckh0foJOVXvzGqvrXcoiBGV8S9Z7c +DghUvMt6b0pZ0ildRCHfTUz7eG3g4MhfbjupR7b+L9FWEJhcd/H0dxpw7SKYha/n +ePuRL7MXmbW8WLMqO/ImxzL8TPOB3pUg3nITfubV6gpPBmn+0nwbqYUmggJuwgvK +I2GpN2Ny6EErZy17EEgyhJygJZMj+UzQjC781xxsl3ljpYEqqwgRLIZBSBUD5dXj +XBuU24w162SeSyHZzkBbuv6lr52pqoZyFrG29DCHECgO9ZmNWgSpiWSkh+vExAG7 +wNs0y61t2HUG+BCMGPQ9sOzouyTfrnLVAWwzswGftFYQfoIBeJIwggb7MIIE46AD +AgECAgMQAAIwDQYJKoZIhvcNAQEMBQAwfTELMAkGA1UEBhMCVVMxHDAaBgNVBAoT +E01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1 +Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rp +b24tYW1vMB4XDTE1MDMxNzIzNTI0MloXDTI1MDMxNDIzNTI0MlowgcUxCzAJBgNV +BAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZN +b3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8GA1UEAxMo +cHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0MDIGCSqG +SIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxhLmNvbTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMLMM9m2HBLhCiO9mhljpehT +hxpzlCnxluzDZ51I/H7MvBbIvZBm9zSpHdffubSsak2qYE69d+ebTa/CK83WIosM +24/2Qp7n/GGaPJcCC4Y3JkrCsgA8+wV2MbFlKSv+qMdvI/sE3BPYDMCjVPMhHmIP +XaPWd42OoHpI8R3GGUtVnR3Hm76pa2+v6TwgeMiO8om+ogGufiyv6FNMZ5NuY1Z9 +aLNEvehnAzSfddQyki+6FJd7XkgZbP7pb1Kl8yYgiy4piBerJ9H09uPehffE3Ell +3cApQL3+0kjaUX4scMjuNQDMKziRZkYgJAM+qA9WA5Jn77AjerQBWQeEev1PWHYh +0IDlgS/a0bjKmVjNZYG6adrY/R5/whzWGFCIE1UfhPm6PdN0557qvF838C2RFHsI +KzV6KQf0chMjpa02tPaIctjVhnDQZZNKm2ZfLOt9kQ57Is/e6KxH7pYMit46+s99 +lYM7ZquvWbK19b1Ili/6S1BxSzd3wztgfN5jGsc+jCCYLm+AcVtfNKc8cFZHXKrB +CwhGmdbWDSBCicZNA7FKJpO3oIx26VPF2XUldA/T5Mh/POGLilK3t9m9qbjEyDp1 +EwoBToOR/aMrdnNYvSWp0g/GHMzSfJjjXyAqrZY2itam/IJd8r9FoRAzevPt/zTX +BET3INoiCDGRH0XrxUYtAgMGVTejggE5MIIBNTAMBgNVHRMEBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdHxf +FKXipZLjs20GqIUdNkQXH4gwgagGA1UdIwSBoDCBnYAUs7zqWHSr4W54KrKrnCMe +qGMsl7ehgYGkfzB9MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jw +b3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5n +IFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW+CAQEwMwYJ +YIZIAYb4QgEEBCYWJGh0dHA6Ly9hZGRvbnMubW96aWxsYS5vcmcvY2EvY3JsLnBl +bTANBgkqhkiG9w0BAQwFAAOCAgEArde/fdjb7TE0eH7Ij7xU4JbcSyhY3cQhVYCw +Fg+Q/2pj+NAfazcjUuLWA0Y/YZs9HOx6j+ZAqO4C/xfMP4RDs9IypxvzHDU6SXgD +RK6uOKtS07HXLcXgFUBvJEQhbT/h5+IQOA4/GcpCshfD6iyiBBi+IocR+tnKPCuZ +T3m1t60Eja/MkPKG/Gx8vSodHvlTTsJ2GzjUEANveCZOnlAdp9fjTvFZny9qqnbg +sfVbuTqKndbCFW5QLXfkna6jBqMrY0+CpMYY2oJ5gwpHbE/7hhukjxGCTcpv7r/O +M53bb/DZnybDlLLepacljvz7DBA1O1FFtEhf9MR+vyvmBpniAyKQhqG2hsVGurE1 +nBcE+oteZWar2lMp6+etDAb9DRC+jZv0aEQs2o/qQwyD8AGquLgBsJq5Jz3gGxzn +4r3vGu2lV8VdzIm0C8sOFSWTmTZxQmJbF8xSsQBojnsvEah4DPER+eAt6qKolaWe +s4drJQjzFyC7HJn2VqalpCwbe9CdMB7eRqzeP6GujJBi80/gx0pAysUtuKKpH5IJ +WbXAOszfrjb3CaHafYZDnwPoOfj74ogFzjt2f54jwnU+ET/byfjZ7J8SLH316C1V +HrvFXcTzyMV4aRluVPjPg9x1G58hMIbeuT4GpwQUNdJ9uL8t65v0XwG2t6Y7jpRO +sFVxBtgxggNXMIIDUwIBATCB0DCBxTELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01v +emlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rp +b24gU2lnbmluZyBTZXJ2aWNlMTEwLwYDVQQDEyhwcm9kdWN0aW9uLXNpZ25pbmct +Y2EuYWRkb25zLm1vemlsbGEub3JnMTQwMgYJKoZIhvcNAQkBFiVzZXJ2aWNlcy1v +cHMrYWRkb25zaWduaW5nQG1vemlsbGEuY29tAgYBVpobWVwwCQYFKw4DAhoFAKBd +MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE2MDgx +NzIwMDQ1OFowIwYJKoZIhvcNAQkEMRYEFAxlGvNFSx+Jqj70haE8b7UZk+2GMA0G +CSqGSIb3DQEBAQUABIICADsDlrucYRgwq9o2QSsO6X6cRa5Zu6w+1n07PTIyc1zn +Pi1cgkkWZ0kZBHDrJ5CY33yRQPl6I1tHXaq7SkOSdOppKhpUmBiKZxQRAZR21QHk +R3v1XS+st/o0N+0btv3YoplUifLIwtH89oolxqlStChELu7FuOBretdhx/z12ytA +EhIIS53o/XjDL7XKJbQA02vzOtOC/Eq6p8BI7F3y6pvtmJIRkeGv+u6ssJa6g5q8 +74w8hHXaH94Z9+hDPqjNWlsXJHgPdAKiEjzDz9oLkvDyX4Pd8JMK5ILskirpG+hj +Q8jkTc5oYwyuSlBAUTGxW6ZbuOrtfVZvOVtRL/ixuiFiVlJ+JOQOxrtK19ukamsI +iacFlbLgiA7w0HCtm2DsT9aL67/1e4rJ0lv0MjnQYUMmKQy7g0Gd3+nQPU9pn+Lf +Z/UmSNWiJ8Csc/seDMyzT6jrzcGPfoSVaUowH0wGrI9If1snwcr+mMg7dWRGf1fm +y/dcVSzed0ax4LqDmike1EshU+51cKWWlnhyNHK4KH+0fNsBQ0c6clrFpGx9MPmV +YXie6C+LWkh5x12RU0sJt/SmSZV6q9VliIkX+yY3jBrC/pKgRahtcIyq46Da1E6K +lc15Euur3NfGow+nott0Z8XutpYdK/2vBKcIh9JOdkd+oe6pcIP6hnhHRp53wqmG +-----END PKCS7-----` + +var FirefoxAddonRootCert = []byte(` +-----BEGIN CERTIFICATE----- +MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBB +TU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2Et +cHJvZHVjdGlvbi1hbW8wHhcNMTUwMzE3MjI1MzU3WhcNMjUwMzE0MjI1MzU3WjB9 +MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0G +A1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAd +BgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW8wggIgMA0GCSqGSIb3DQEBAQUA +A4ICDQAwggIIAoICAQC0u2HXXbrwy36+MPeKf5jgoASMfMNz7mJWBecJgvlTf4hH +JbLzMPsIUauzI9GEpLfHdZ6wzSyFOb4AM+D1mxAWhuZJ3MDAJOf3B1Rs6QorHrl8 +qqlNtPGqepnpNJcLo7JsSqqE3NUm72MgqIHRgTRsqUs+7LIPGe7262U+N/T0LPYV +Le4rZ2RDHoaZhYY7a9+49mHOI/g2YFB+9yZjE+XdplT2kBgA4P8db7i7I0tIi4b0 +B0N6y9MhL+CRZJyxdFe2wBykJX14LsheKsM1azHjZO56SKNrW8VAJTLkpRxCmsiT +r08fnPyDKmaeZ0BtsugicdipcZpXriIGmsZbI12q5yuwjSELdkDV6Uajo2n+2ws5 +uXrP342X71WiWhC/dF5dz1LKtjBdmUkxaQMOP/uhtXEKBrZo1ounDRQx1j7+SkQ4 +BEwjB3SEtr7XDWGOcOIkoJZWPACfBLC3PJCBWjTAyBlud0C5n3Cy9regAAnOIqI1 +t16GU2laRh7elJ7gPRNgQgwLXeZcFxw6wvyiEcmCjOEQ6PM8UQjthOsKlszMhlKw +vjyOGDoztkqSBy/v+Asx7OW2Q7rlVfKarL0mREZdSMfoy3zTgtMVCM0vhNl6zcvf +5HNNopoEdg5yuXo2chZ1p1J+q86b0G5yJRMeT2+iOVY2EQ37tHrqUURncCy4uwIB +A6OB7TCB6jAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8E +DDAKBggrBgEFBQcDAzCBkgYDVR0jBIGKMIGHoYGBpH8wfTELMAkGA1UEBhMCVVMx +HDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEg +QU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNh +LXByb2R1Y3Rpb24tYW1vggEBMB0GA1UdDgQWBBSzvOpYdKvhbngqsqucIx6oYyyX +tzANBgkqhkiG9w0BAQwFAAOCAgEAaNSRYAaECAePQFyfk12kl8UPLh8hBNidP2H6 +KT6O0vCVBjxmMrwr8Aqz6NL+TgdPmGRPDDLPDpDJTdWzdj7khAjxqWYhutACTew5 +eWEaAzyErbKQl+duKvtThhV2p6F6YHJ2vutu4KIciOMKB8dslIqIQr90IX2Usljq +8Ttdyf+GhUmazqLtoB0GOuESEqT4unX6X7vSGu1oLV20t7t5eCnMMYD67ZBn0YIU +/cm/+pan66hHrja+NeDGF8wabJxdqKItCS3p3GN1zUGuJKrLykxqbOp/21byAGog +Z1amhz6NHUcfE6jki7sM7LHjPostU5ZWs3PEfVVgha9fZUhOrIDsyXEpCWVa3481 +LlAq3GiUMKZ5DVRh9/Nvm4NwrTfB3QkQQJCwfXvO9pwnPKtISYkZUqhEqvXk5nBg +QCkDSLDjXTx39naBBGIVIqBtKKuVTla9enngdq692xX/CgO6QJVrwpqdGjebj5P8 +5fNZPABzTezG3Uls5Vp+4iIWVAEDkK23cUj3c/HhE+Oo7kxfUeu5Y1ZV3qr61+6t +ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL +26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT +wNuvFqc= +-----END CERTIFICATE-----`) + +// sign a document with openssl and verify the signature with pkcs7. +// this uses a chain of root, intermediate and signer cert, where the +// intermediate is added to the certs but the root isn't. +func TestSignWithOpenSSLAndVerify(t *testing.T) { + content := []byte(` +A ship in port is safe, +but that's not what ships are built for. +-- Grace Hopper`) + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + sigalgs := []x509.SignatureAlgorithm{ + x509.SHA1WithRSA, + x509.SHA256WithRSA, + x509.SHA512WithRSA, + x509.ECDSAWithSHA1, + x509.ECDSAWithSHA256, + x509.ECDSAWithSHA384, + x509.ECDSAWithSHA512, + } + for _, sigalgroot := range sigalgs { + rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) + if err != nil { + t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) + } + truststore := x509.NewCertPool() + truststore.AddCert(rootCert.Certificate) + for _, sigalginter := range sigalgs { + interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) + if err != nil { + t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) + } + // write the intermediate cert to a temp file + tmpInterCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_intermediate") + if err != nil { + t.Fatal(err) + } + fd, err := os.OpenFile(tmpInterCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: interCert.Certificate.Raw}) + fd.Close() + for _, sigalgsigner := range sigalgs { + signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) + if err != nil { + t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) + } + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signer") + if err != nil { + t.Fatal(err) + } + fd, err = os.OpenFile(tmpSignerCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Certificate.Raw}) + fd.Close() + + // write the signer key to a temp file + tmpSignerKeyFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_key") + if err != nil { + t.Fatal(err) + } + fd, err = os.OpenFile(tmpSignerKeyFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + t.Fatal(err) + } + var derKey []byte + priv := *signerCert.PrivateKey + switch priv := priv.(type) { + case *rsa.PrivateKey: + derKey = x509.MarshalPKCS1PrivateKey(priv) + pem.Encode(fd, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey}) + case *ecdsa.PrivateKey: + derKey, err = x509.MarshalECPrivateKey(priv) + if err != nil { + t.Fatal(err) + } + pem.Encode(fd, &pem.Block{Type: "EC PRIVATE KEY", Bytes: derKey}) + } + fd.Close() + + // write the root cert to a temp file + tmpSignedFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signature") + if err != nil { + t.Fatal(err) + } + // call openssl to sign the content + opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", + "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), + "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), + "-certfile", tmpInterCertFile.Name(), "-outform", "PEM") + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("test %s/%s/%s: openssl command failed with %s: %s", sigalgroot, sigalginter, sigalgsigner, err, out) + } + + // verify the signed content + pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) + if err != nil { + t.Fatal(err) + } + derBlock, _ := pem.Decode(pemSignature) + if derBlock == nil { + break + } + p7, err := Parse(derBlock.Bytes) + if err != nil { + t.Fatalf("Parse encountered unexpected error: %v", err) + } + if err := p7.VerifyWithChain(truststore); err != nil { + t.Fatalf("Verify failed with error: %v", err) + } + // Verify the certificate chain to make sure the identified root + // is the one we expect + ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) + if ee == nil { + t.Fatalf("No end-entity certificate found for signer") + } + chains, err := verifyCertChain(ee, p7.Certificates, truststore, time.Now()) + if err != nil { + t.Fatal(err) + } + if len(chains) != 1 { + t.Fatalf("Expected to find one chain, but found %d", len(chains)) + } + if len(chains[0]) != 3 { + t.Fatalf("Expected to find three certificates in chain, but found %d", len(chains[0])) + } + if chains[0][0].Subject.CommonName != "PKCS7 Test Signer Cert" { + t.Fatalf("Expected to find EE certificate with subject 'PKCS7 Test Signer Cert', but found '%s'", chains[0][0].Subject.CommonName) + } + if chains[0][1].Subject.CommonName != "PKCS7 Test Intermediate Cert" { + t.Fatalf("Expected to find intermediate certificate with subject 'PKCS7 Test Intermediate Cert', but found '%s'", chains[0][1].Subject.CommonName) + } + if chains[0][2].Subject.CommonName != "PKCS7 Test Root CA" { + t.Fatalf("Expected to find root certificate with subject 'PKCS7 Test Root CA', but found '%s'", chains[0][2].Subject.CommonName) + } + os.Remove(tmpSignerCertFile.Name()) // clean up + os.Remove(tmpSignerKeyFile.Name()) // clean up + } + os.Remove(tmpInterCertFile.Name()) // clean up + } + } + os.Remove(tmpContentFile.Name()) // clean up +} + +func TestDSASignWithOpenSSLAndVerify(t *testing.T) { + content := []byte(` +A ship in port is safe, +but that's not what ships are built for. +-- Grace Hopper`) + // write the content to a temp file + tmpContentFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_content") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpContentFile.Name(), content, 0755) + + // write the signer cert to a temp file + tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signer") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) + + // write the signer key to a temp file + tmpSignerKeyFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_key") + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(tmpSignerKeyFile.Name(), dsaPrivateKey, 0755) + + tmpSignedFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signature") + if err != nil { + t.Fatal(err) + } + // call openssl to sign the content + opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", "-md", "sha1", + "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), + "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), + "-certfile", tmpSignerCertFile.Name(), "-outform", "PEM") + out, err := opensslCMD.CombinedOutput() + if err != nil { + t.Fatalf("openssl command failed with %s: %s", err, out) + } + + // verify the signed content + pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) + if err != nil { + t.Fatal(err) + } + fmt.Printf("%s\n", pemSignature) + derBlock, _ := pem.Decode(pemSignature) + if derBlock == nil { + t.Fatalf("failed to read DER block from signature PEM %s", tmpSignedFile.Name()) + } + p7, err := Parse(derBlock.Bytes) + if err != nil { + t.Fatalf("Parse encountered unexpected error: %v", err) + } + if err := p7.Verify(); err != nil { + t.Fatalf("Verify failed with error: %v", err) + } + os.Remove(tmpSignerCertFile.Name()) // clean up + os.Remove(tmpSignerKeyFile.Name()) // clean up + os.Remove(tmpContentFile.Name()) // clean up +} + +var dsaPrivateKey = []byte(`-----BEGIN PRIVATE KEY----- +MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS +PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl +pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith +1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L +vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 +zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo +g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUfW4aPdQBn9gJZp2KuNpzgHzvfsE= +-----END PRIVATE KEY-----`) + +var dsaPublicCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDOjCCAvWgAwIBAgIEPCY/UDANBglghkgBZQMEAwIFADBsMRAwDgYDVQQGEwdV +bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD +VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du +MB4XDTE4MTAyMjEzNDMwN1oXDTQ2MDMwOTEzNDMwN1owbDEQMA4GA1UEBhMHVW5r +bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE +ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC +AbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD +Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE +exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii +Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4 +V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI +puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl +nwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDCriMPbEVBoRK4SOUeFwg7+VRf4TTp +rcOQC9IVVoCjXzuWEGrp3ZI7YWJSpFnSch4lk29RH8O0HpI/NOzKnOBtnKr782pt +1k/bJVMH9EaLd6MKnAVjrCDMYBB0MhebZ8QHY2elZZCWoqDYAcIDOsEx+m4NLErT +ypPnjS5M0jm1PKMhMB8wHQYDVR0OBBYEFC0Yt5XdM0Kc95IX8NQ8XRssGPx7MA0G +CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688 +qzy/7yePTlhlpj+ahMM= +-----END CERTIFICATE-----`) From 7ad90d10b3e5a07bbe209e3c378c58855ab072aa Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 00:32:21 +0100 Subject: [PATCH 07/42] Refactor initialization of SCEP authority --- authority/authority.go | 45 ++++++ authority/provisioner/scep.go | 4 +- ca/ca.go | 3 +- cas/apiv1/options.go | 3 +- cas/softcas/softcas_test.go | 4 + go.mod | 2 +- go.sum | 21 ++- kms/apiv1/options.go | 1 + kms/apiv1/requests.go | 11 ++ kms/awskms/awskms.go | 6 + kms/cloudkms/cloudkms.go | 6 + kms/pkcs11/pkcs11.go | 5 + kms/softkms/softkms.go | 36 +++++ kms/sshagentkms/sshagentkms.go | 6 + kms/yubikey/yubikey.go | 6 + scep/api/api.go | 47 +++--- scep/authority.go | 279 ++++++++++++++++++++++++++++----- scep/scep.go | 54 +++++++ scep/service.go | 9 ++ 19 files changed, 476 insertions(+), 72 deletions(-) create mode 100644 scep/scep.go create mode 100644 scep/service.go diff --git a/authority/authority.go b/authority/authority.go index 37fd0429..f0e45808 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -11,6 +11,7 @@ import ( "time" "github.com/smallstep/certificates/cas" + "github.com/smallstep/certificates/scep" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" @@ -42,6 +43,9 @@ type Authority struct { federatedX509Certs []*x509.Certificate certificates *sync.Map + // SCEP CA + scepService *scep.Service + // SSH CA sshCAUserCertSignKey ssh.Signer sshCAHostCertSignKey ssh.Signer @@ -196,6 +200,38 @@ func (a *Authority) init() error { } } + // TODO: decide if this is a good approach for providing the SCEP functionality + if a.scepService == nil { + var options casapi.Options + if a.config.AuthorityConfig.Options != nil { + options = *a.config.AuthorityConfig.Options + } + + // Read intermediate and create X509 signer for default CAS. + if options.Is(casapi.SoftCAS) { + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + } + + a.scepService = &scep.Service{ + Signer: options.Signer, + Decrypter: options.Decrypter, + } + } + // Read root certificates and store them in the certificates map. if len(a.rootX509Certs) == 0 { a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) @@ -394,3 +430,12 @@ func (a *Authority) CloseForReload() { log.Printf("error closing the key manager: %v", err) } } + +// GetSCEPService returns the configured SCEP Service +// TODO: this function is intended to exist temporarily +// in order to make SCEP work more easily. It can be +// made more correct by using the right interfaces/abstractions +// after it works as expected. +func (a *Authority) GetSCEPService() scep.Service { + return *a.scepService +} diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 30b4a1b2..6cdfa69f 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -55,7 +55,7 @@ func (s *SCEP) DefaultTLSCertDuration() time.Duration { return s.claimer.DefaultTLSCertDuration() } -// Init initializes and validates the fields of a JWK type. +// Init initializes and validates the fields of a SCEP type. func (s *SCEP) Init(config Config) (err error) { switch { @@ -70,6 +70,8 @@ func (s *SCEP) Init(config Config) (err error) { return err } + // TODO: add other, SCEP specific, options? + return err } diff --git a/ca/ca.go b/ca/ca.go index f256a5e4..d062cbef 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -156,10 +156,11 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { // well as certificates via SCEP. tlsConfig = nil + // TODO: get the SCEP service scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ IntermediateCertificatePath: config.IntermediateCert, - IntermediateKeyPath: config.IntermediateKey, + Service: auth.GetSCEPService(), Backdate: *config.AuthorityConfig.Backdate, DB: auth.GetDatabase().(nosql.DB), DNS: dns, diff --git a/cas/apiv1/options.go b/cas/apiv1/options.go index 46efae3b..b6b0ca0d 100644 --- a/cas/apiv1/options.go +++ b/cas/apiv1/options.go @@ -24,7 +24,8 @@ type Options struct { // Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS. // They are configured in ca.json crt and key properties. CertificateChain []*x509.Certificate - Signer crypto.Signer `json:"-"` + Signer crypto.Signer `json:"-"` + Decrypter crypto.Decrypter `json:"-"` // IsCreator is set to true when we're creating a certificate authority. Is // used to skip some validations when initializing a CertificateAuthority. diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 0a50a990..092a0337 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -110,6 +110,10 @@ func (m *mockKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (crypto.S return signer, m.errCreatesigner } +func (m *mockKeyManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, nil +} + func (m *mockKeyManager) Close() error { return m.errClose } diff --git a/go.mod b/go.mod index 8ef1b088..921067ea 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +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/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 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 2d212432..4a52e58e 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -103,10 +104,16 @@ github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo= +github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U= github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk= +github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -177,6 +184,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -202,6 +212,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -223,8 +235,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/micromdm/scep v1.0.0 h1:ai//kcZnxZPq1YE/MatiE2bIRD94KOAwZRpN1fhVQXY= -github.com/micromdm/scep v1.0.0/go.mod h1:CID2SixSr5FvoauZdAFUSpQkn5MAuSy9oyURMGOJbag= +github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA= +github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8= 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/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -236,6 +248,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -291,6 +304,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -343,6 +358,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -387,6 +403,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 705f3633..0e6f32df 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -13,6 +13,7 @@ type KeyManager interface { GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) + CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface? Close() error } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index e58c4546..31097040 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -133,6 +133,17 @@ type CreateSignerRequest struct { Password []byte } +// CreateDecrypterRequest is the parameter used in the kms.Decrypt method. +type CreateDecrypterRequest struct { + Decrypter crypto.Decrypter + DecryptionKey string + DecryptionKeyPEM []byte + TokenLabel string + PublicKey string + PublicKeyPEM []byte + Password []byte +} + // LoadCertificateRequest is the parameter used in the LoadCertificate method of // a CertificateManager. type LoadCertificateRequest struct { diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index da392989..00d7d0b3 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -3,6 +3,7 @@ package awskms import ( "context" "crypto" + "fmt" "net/url" "strings" "time" @@ -221,6 +222,11 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error return NewSigner(k.service, req.SigningKey) } +// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS +func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index cc533702..ca6513f7 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -3,6 +3,7 @@ package cloudkms import ( "context" "crypto" + "fmt" "log" "strings" "time" @@ -285,6 +286,11 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe return pk, nil } +// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS +func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // getPublicKeyWithRetries retries the request if the error is // FailedPrecondition, caused because the key is in the PENDING_GENERATION // status. diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 47c298a5..8c1497e8 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -352,3 +352,8 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { } return cert, nil } + +// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11 +func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} diff --git a/kms/softkms/softkms.go b/kms/softkms/softkms.go index 23b50849..a2f43c31 100644 --- a/kms/softkms/softkms.go +++ b/kms/softkms/softkms.go @@ -145,3 +145,39 @@ func (k *SoftKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey return nil, errors.Errorf("unsupported public key type %T", v) } } + +// CreateDecrypter creates a new crypto.Decrypter backed by disk/software +func (k *SoftKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + + var opts []pemutil.Options + if req.Password != nil { + opts = append(opts, pemutil.WithPassword(req.Password)) + } + + switch { + case req.Decrypter != nil: + return req.Decrypter, nil + case len(req.DecryptionKeyPEM) != 0: + v, err := pemutil.ParseKey(req.DecryptionKeyPEM, opts...) + if err != nil { + return nil, err + } + decrypter, ok := v.(crypto.Decrypter) + if !ok { + return nil, errors.New("decryptorKeyPEM is not a crypto.Decrypter") + } + return decrypter, nil + case req.DecryptionKey != "": + v, err := pemutil.Read(req.DecryptionKey, opts...) + if err != nil { + return nil, err + } + decrypter, ok := v.(crypto.Decrypter) + if !ok { + return nil, errors.New("decryptionKey is not a crypto.Decrypter") + } + return decrypter, nil + default: + return nil, errors.New("failed to load softKMS: please define decryptionKeyPEM or decryptionKey") + } +} diff --git a/kms/sshagentkms/sshagentkms.go b/kms/sshagentkms/sshagentkms.go index b3627a08..9c8a7866 100644 --- a/kms/sshagentkms/sshagentkms.go +++ b/kms/sshagentkms/sshagentkms.go @@ -7,6 +7,7 @@ import ( "crypto/ed25519" "crypto/rsa" "crypto/x509" + "fmt" "io" "net" "os" @@ -204,3 +205,8 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi return nil, errors.Errorf("unsupported public key type %T", v) } } + +// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent +func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index 19cef55e..45ee3d87 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -7,6 +7,7 @@ import ( "crypto" "crypto/x509" "encoding/hex" + "fmt" "net/url" "strings" @@ -189,6 +190,11 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e return signer, nil } +// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey +func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { + return nil, fmt.Errorf("not implemented yet") +} + // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { return errors.Wrap(k.yk.Close(), "error closing yubikey") diff --git a/scep/api/api.go b/scep/api/api.go index 5a4cb5b2..ec959519 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -28,6 +28,8 @@ const ( opnGetCACert = "GetCACert" opnGetCACaps = "GetCACaps" opnPKIOperation = "PKIOperation" + + // TODO: add other (more optional) operations and handling ) // SCEPRequest is a SCEP server request. @@ -98,6 +100,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { } func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { + scepRequest, err := decodeSCEPRequest(r) if err != nil { fmt.Println(err) @@ -252,27 +255,22 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque return err } - certs, err := h.Auth.GetCACertificates() - if err != nil { + pkimsg := &scep.PKIMessage{ + TransactionID: msg.TransactionID, + MessageType: msg.MessageType, + SenderNonce: msg.SenderNonce, + Raw: msg.Raw, + } + + if err := h.Auth.DecryptPKIEnvelope(pkimsg); err != nil { return err } - // TODO: instead of getting the key to decrypt, add a decrypt function to the auth; less leaky - key, err := h.Auth.GetSigningKey() - if err != nil { - return err - } - - ca := certs[0] - if err := msg.DecryptPKIEnvelope(ca, key); err != nil { - return err - } - - if msg.MessageType == microscep.PKCSReq { + if pkimsg.MessageType == microscep.PKCSReq { // TODO: CSR validation, like challenge password } - csr := msg.CSRReqMessage.CSR + csr := pkimsg.CSRReqMessage.CSR id, err := createKeyIdentifier(csr.PublicKey) if err != nil { return err @@ -282,6 +280,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque days := 40 + // TODO: use information from provisioner, like claims template := &x509.Certificate{ SerialNumber: serial, Subject: csr.Subject, @@ -296,16 +295,16 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque EmailAddresses: csr.EmailAddresses, } - certRep, err := msg.SignCSR(ca, key, template) + certRep, err := h.Auth.SignCSR(pkimsg, template) if err != nil { return err } - //cert := certRep.CertRepMessage.Certificate - //name := certName(cert) + // //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; should go into the DB + // // 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; should go into the DB scepResponse.Data = certRep.Raw @@ -321,7 +320,7 @@ func certName(cert *x509.Certificate) string { return string(cert.Signature) } -// createKeyIdentifier create an identifier for public keys +// createKeyIdentifier creates an identifier for public keys // according to the first method in RFC5280 section 4.2.1.2. func createKeyIdentifier(pub crypto.PublicKey) ([]byte, error) { @@ -390,9 +389,9 @@ func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { if val == nil { return nil, errors.New("provisioner expected in request context") } - pval, ok := val.(scep.Provisioner) - if !ok || pval == nil { + p, ok := val.(scep.Provisioner) + if !ok || p == nil { return nil, errors.New("provisioner in context is not a SCEP provisioner") } - return pval, nil + return p, nil } diff --git a/scep/authority.go b/scep/authority.go index 27817b0f..244a6f92 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,17 +1,26 @@ package scep import ( - "crypto/rsa" + "bytes" "crypto/x509" - "encoding/pem" "errors" - "io/ioutil" + "fmt" + "math/big" + "math/rand" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" + "go.step.sm/crypto/pemutil" "github.com/smallstep/nosql" + + microx509util "github.com/micromdm/scep/crypto/x509util" + microscep "github.com/micromdm/scep/scep" + + "github.com/smallstep/certificates/scep/pkcs7" + + "go.step.sm/crypto/x509util" ) // Interface is the SCEP authority interface. @@ -41,7 +50,10 @@ type Interface interface { // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) - GetSigningKey() (*rsa.PrivateKey, error) + //GetSigningKey() (*rsa.PrivateKey, error) + + DecryptPKIEnvelope(*PKIMessage) error + SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -54,17 +66,16 @@ type Authority struct { // dir *directory intermediateCertificate *x509.Certificate - intermediateKey *rsa.PrivateKey - - //signer crypto.Signer + service Service signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { IntermediateCertificatePath string - IntermediateKeyPath string + + Service Service // Backdate Backdate provisioner.Duration @@ -79,7 +90,7 @@ type AuthorityOptions struct { Prefix string } -// SignAuthority is the interface implemented by a CA authority. +// SignAuthority is the interface for a signing authority type SignAuthority interface { Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByID(string) (provisioner.Interface, error) @@ -87,6 +98,7 @@ type SignAuthority interface { // 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 } @@ -100,42 +112,17 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { return nil, err } - intermediateKey, err := readPrivateKey(ops.IntermediateKeyPath) - if err != nil { - return nil, err - } - return &Authority{ backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, intermediateCertificate: certificateChain[0], - intermediateKey: intermediateKey, + service: ops.Service, signAuth: signAuth, }, nil } -func readPrivateKey(path string) (*rsa.PrivateKey, error) { - - keyBytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - block, _ := pem.Decode([]byte(keyBytes)) - if block == nil { - return nil, nil - } - - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - - return key, nil -} - // LoadProvisionerByID calls out to the SignAuthority interface to load a // provisioner by ID. func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { @@ -145,6 +132,20 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error // GetCACertificates returns the certificate (chain) for the CA func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { + // TODO: this should return: the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root + // Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73 + // + // This means we might need to think about if we should use the current intermediate CA + // certificate as the "SCEP Server (RA)" certificate. It might be better to have a distinct + // RA certificate, with a corresponding rsa.PrivateKey, just for SCEP usage, which is signed by + // the intermediate CA. Will need to look how we can provide this nicely within step-ca. + // + // This might also mean that we might want to use a distinct instance of KMS for doing the key operations, + // so that we can use RSA just for SCEP. + // + // 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. + if a.intermediateCertificate == nil { return nil, errors.New("no intermediate certificate available in SCEP authority") } @@ -152,16 +153,210 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { return []*x509.Certificate{a.intermediateCertificate}, nil } -// GetSigningKey returns the RSA private key for the CA -// TODO: we likely should provide utility functions for decrypting and -// signing instead of providing the signing key directly -func (a *Authority) GetSigningKey() (*rsa.PrivateKey, error) { +// DecryptPKIEnvelope decrypts an enveloped message +func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { - if a.intermediateKey == nil { - return nil, errors.New("no intermediate key available in SCEP authority") + data := msg.Raw + + p7, err := pkcs7.Parse(data) + if err != nil { + return err } - return a.intermediateKey, nil + var tID microscep.TransactionID + if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { + return err + } + + var msgType microscep.MessageType + if err := p7.UnmarshalSignedAttribute(oidSCEPmessageType, &msgType); err != nil { + return err + } + + msg.p7 = p7 + + p7c, err := pkcs7.Parse(p7.Content) + if err != nil { + return err + } + + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) + if err != nil { + return err + } + + msg.pkiEnvelope = envelope + + switch msg.MessageType { + case microscep.CertRep: + certs, err := microscep.CACerts(msg.pkiEnvelope) + if err != nil { + return err + } + msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this + return nil + case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: + csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) + if err != nil { + return fmt.Errorf("parse CSR from pkiEnvelope") + } + // check for challengePassword + cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) + if err != nil { + return fmt.Errorf("scep: parse challenge password in pkiEnvelope") + } + msg.CSRReqMessage = µscep.CSRReqMessage{ + RawDecrypted: msg.pkiEnvelope, + CSR: csr, + ChallengePassword: cp, + } + //msg.Certificate = p7.Certificates[0] // TODO: check if this is necessary to add (again) + return nil + case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: + return fmt.Errorf("not implemented") //errNotImplemented + } + + return nil +} + +// SignCSR creates an x509.Certificate based on a template and Cert Authority credentials +// returns a new PKIMessage with CertRep data +//func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + + // check if CSRReqMessage has already been decrypted + if msg.CSRReqMessage.CSR == nil { + if err := a.DecryptPKIEnvelope(msg); err != nil { + return nil, err + } + } + + csr := msg.CSRReqMessage.CSR + + // Template data + data := x509util.NewTemplateData() + data.SetCommonName(csr.Subject.CommonName) + //data.Set(x509util.SANsKey, sans) + + // templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) + // if err != nil { + // return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner")) + // } + // signOps = append(signOps, templateOptions) + + // // Create and store a new certificate. + // certChain, err := auth.Sign(csr, provisioner.SignOptions{ + // NotBefore: provisioner.NewTimeDuration(o.NotBefore), + // NotAfter: provisioner.NewTimeDuration(o.NotAfter), + // }, signOps...) + // if err != nil { + // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) + // } + + // TODO: proper options + signOps := provisioner.SignOptions{} + signOps2 := []provisioner.SignOption{} + + certs, err := a.signAuth.Sign(csr, signOps, signOps2...) + if err != nil { + return nil, err + } + + cert := certs[0] + + // fmt.Println("CERT") + // fmt.Println(cert) + // fmt.Println(fmt.Sprintf("%T", cert)) + // fmt.Println(cert.Issuer) + // fmt.Println(cert.Subject) + + serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + cert.SerialNumber = serial + + // create a degenerate cert structure + deg, err := DegenerateCertificates([]*x509.Certificate{cert}) + if err != nil { + return nil, err + } + + e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates) + if err != nil { + return nil, err + } + + // PKIMessageAttributes to be signed + config := pkcs7.SignerInfoConfig{ + ExtraSignedAttributes: []pkcs7.Attribute{ + { + Type: oidSCEPtransactionID, + Value: msg.TransactionID, + }, + { + Type: oidSCEPpkiStatus, + Value: microscep.SUCCESS, + }, + { + Type: oidSCEPmessageType, + Value: microscep.CertRep, + }, + { + Type: oidSCEPrecipientNonce, + Value: msg.SenderNonce, + }, + }, + } + + signedData, err := pkcs7.NewSignedData(e7) + if err != nil { + return nil, err + } + + // add the certificate into the signed data type + // this cert must be added before the signedData because the recipient will expect it + // as the first certificate in the array + signedData.AddCertificate(cert) + + authCert := a.intermediateCertificate + + // sign the attributes + if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil { + return nil, err + } + + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } + + cr := &CertRepMessage{ + PKIStatus: microscep.SUCCESS, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + Certificate: cert, + degenerate: deg, + } + + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, + } + + return crepMsg, nil +} + +// DegenerateCertificates creates degenerate certificates pkcs#7 type +func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { + var buf bytes.Buffer + for _, cert := range certs { + buf.Write(cert.Raw) + } + degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes()) + if err != nil { + return nil, err + } + return degenerate, nil } // Interface guards diff --git a/scep/scep.go b/scep/scep.go new file mode 100644 index 00000000..bc46cce7 --- /dev/null +++ b/scep/scep.go @@ -0,0 +1,54 @@ +package scep + +import ( + "crypto/x509" + "encoding/asn1" + + microscep "github.com/micromdm/scep/scep" + + "github.com/smallstep/certificates/scep/pkcs7" +) + +// SCEP OIDs +var ( + oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2} + oidSCEPpkiStatus = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 3} + oidSCEPfailInfo = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 4} + oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5} + oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} + oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} + oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} +) + +// PKIMessage defines the possible SCEP message types +type PKIMessage struct { + microscep.TransactionID + microscep.MessageType + microscep.SenderNonce + *microscep.CSRReqMessage + + *CertRepMessage + + // DER Encoded PKIMessage + Raw []byte + + // parsed + p7 *pkcs7.PKCS7 + + // decrypted enveloped content + pkiEnvelope []byte + + // Used to sign message + Recipients []*x509.Certificate +} + +// CertRepMessage is a type of PKIMessage +type CertRepMessage struct { + microscep.PKIStatus + microscep.RecipientNonce + microscep.FailInfo + + Certificate *x509.Certificate + + degenerate []byte +} diff --git a/scep/service.go b/scep/service.go new file mode 100644 index 00000000..1d743dd6 --- /dev/null +++ b/scep/service.go @@ -0,0 +1,9 @@ +package scep + +import "crypto" + +// Service is a (temporary?) wrapper for signer/decrypters +type Service struct { + Signer crypto.Signer + Decrypter crypto.Decrypter +} From 19f0397fe96f665fc4e522375fd68caafbe06996 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 00:55:37 +0100 Subject: [PATCH 08/42] Remove the copy of mozilla/pkcs7 Apparently the existing library works out of the box, after all. We'll have to see how it works out continuing forward. --- scep/authority.go | 4 +- scep/pkcs7/.gitignore | 24 -- scep/pkcs7/.travis.yml | 10 - scep/pkcs7/LICENSE | 22 -- scep/pkcs7/Makefile | 20 -- scep/pkcs7/README.md | 69 ---- scep/pkcs7/ber.go | 251 ------------- scep/pkcs7/ber_test.go | 62 ---- scep/pkcs7/decrypt.go | 177 --------- scep/pkcs7/decrypt_test.go | 60 ---- scep/pkcs7/encrypt.go | 399 --------------------- scep/pkcs7/encrypt_test.go | 102 ------ scep/pkcs7/pkcs7.go | 291 --------------- scep/pkcs7/pkcs7_test.go | 326 ----------------- scep/pkcs7/sign.go | 429 ---------------------- scep/pkcs7/sign_test.go | 266 -------------- scep/pkcs7/verify.go | 264 -------------- scep/pkcs7/verify_test.go | 713 ------------------------------------- scep/scep.go | 4 +- 19 files changed, 6 insertions(+), 3487 deletions(-) delete mode 100644 scep/pkcs7/.gitignore delete mode 100644 scep/pkcs7/.travis.yml delete mode 100644 scep/pkcs7/LICENSE delete mode 100644 scep/pkcs7/Makefile delete mode 100644 scep/pkcs7/README.md delete mode 100644 scep/pkcs7/ber.go delete mode 100644 scep/pkcs7/ber_test.go delete mode 100644 scep/pkcs7/decrypt.go delete mode 100644 scep/pkcs7/decrypt_test.go delete mode 100644 scep/pkcs7/encrypt.go delete mode 100644 scep/pkcs7/encrypt_test.go delete mode 100644 scep/pkcs7/pkcs7.go delete mode 100644 scep/pkcs7/pkcs7_test.go delete mode 100644 scep/pkcs7/sign.go delete mode 100644 scep/pkcs7/sign_test.go delete mode 100644 scep/pkcs7/verify.go delete mode 100644 scep/pkcs7/verify_test.go diff --git a/scep/authority.go b/scep/authority.go index 244a6f92..864ecbba 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -18,7 +18,9 @@ import ( microx509util "github.com/micromdm/scep/crypto/x509util" microscep "github.com/micromdm/scep/scep" - "github.com/smallstep/certificates/scep/pkcs7" + //"github.com/smallstep/certificates/scep/pkcs7" + + "go.mozilla.org/pkcs7" "go.step.sm/crypto/x509util" ) diff --git a/scep/pkcs7/.gitignore b/scep/pkcs7/.gitignore deleted file mode 100644 index daf913b1..00000000 --- a/scep/pkcs7/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/scep/pkcs7/.travis.yml b/scep/pkcs7/.travis.yml deleted file mode 100644 index eac4c176..00000000 --- a/scep/pkcs7/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go -go: - - "1.11" - - "1.12" - - "1.13" - - tip -before_install: - - make gettools -script: - - make diff --git a/scep/pkcs7/LICENSE b/scep/pkcs7/LICENSE deleted file mode 100644 index 75f32090..00000000 --- a/scep/pkcs7/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 Andrew Smith - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/scep/pkcs7/Makefile b/scep/pkcs7/Makefile deleted file mode 100644 index 47c73b86..00000000 --- a/scep/pkcs7/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -all: vet staticcheck test - -test: - go test -covermode=count -coverprofile=coverage.out . - -showcoverage: test - go tool cover -html=coverage.out - -vet: - go vet . - -lint: - golint . - -staticcheck: - staticcheck . - -gettools: - go get -u honnef.co/go/tools/... - go get -u golang.org/x/lint/golint diff --git a/scep/pkcs7/README.md b/scep/pkcs7/README.md deleted file mode 100644 index bf37059c..00000000 --- a/scep/pkcs7/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# pkcs7 - -[![GoDoc](https://godoc.org/go.mozilla.org/pkcs7?status.svg)](https://godoc.org/go.mozilla.org/pkcs7) -[![Build Status](https://travis-ci.org/mozilla-services/pkcs7.svg?branch=master)](https://travis-ci.org/mozilla-services/pkcs7) - -pkcs7 implements parsing and creating signed and enveloped messages. - -```go -package main - -import ( - "bytes" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "os" - - "go.mozilla.org/pkcs7" -) - -func SignAndDetach(content []byte, cert *x509.Certificate, privkey *rsa.PrivateKey) (signed []byte, err error) { - toBeSigned, err := NewSignedData(content) - if err != nil { - err = fmt.Errorf("Cannot initialize signed data: %s", err) - return - } - if err = toBeSigned.AddSigner(cert, privkey, SignerInfoConfig{}); err != nil { - err = fmt.Errorf("Cannot add signer: %s", err) - return - } - - // Detach signature, omit if you want an embedded signature - toBeSigned.Detach() - - signed, err = toBeSigned.Finish() - if err != nil { - err = fmt.Errorf("Cannot finish signing data: %s", err) - return - } - - // Verify the signature - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) - p7, err := pkcs7.Parse(signed) - if err != nil { - err = fmt.Errorf("Cannot parse our signed data: %s", err) - return - } - - // since the signature was detached, reattach the content here - p7.Content = content - - if bytes.Compare(content, p7.Content) != 0 { - err = fmt.Errorf("Our content was not in the parsed data:\n\tExpected: %s\n\tActual: %s", content, p7.Content) - return - } - if err = p7.Verify(); err != nil { - err = fmt.Errorf("Cannot verify our signed data: %s", err) - return - } - - return signed, nil -} -``` - - - -## Credits -This is a fork of [fullsailor/pkcs7](https://github.com/fullsailor/pkcs7) diff --git a/scep/pkcs7/ber.go b/scep/pkcs7/ber.go deleted file mode 100644 index 58525673..00000000 --- a/scep/pkcs7/ber.go +++ /dev/null @@ -1,251 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "errors" -) - -var encodeIndent = 0 - -type asn1Object interface { - EncodeTo(writer *bytes.Buffer) error -} - -type asn1Structured struct { - tagBytes []byte - content []asn1Object -} - -func (s asn1Structured) EncodeTo(out *bytes.Buffer) error { - //fmt.Printf("%s--> tag: % X\n", strings.Repeat("| ", encodeIndent), s.tagBytes) - encodeIndent++ - inner := new(bytes.Buffer) - for _, obj := range s.content { - err := obj.EncodeTo(inner) - if err != nil { - return err - } - } - encodeIndent-- - out.Write(s.tagBytes) - encodeLength(out, inner.Len()) - out.Write(inner.Bytes()) - return nil -} - -type asn1Primitive struct { - tagBytes []byte - length int - content []byte -} - -func (p asn1Primitive) EncodeTo(out *bytes.Buffer) error { - _, err := out.Write(p.tagBytes) - if err != nil { - return err - } - if err = encodeLength(out, p.length); err != nil { - return err - } - //fmt.Printf("%s--> tag: % X length: %d\n", strings.Repeat("| ", encodeIndent), p.tagBytes, p.length) - //fmt.Printf("%s--> content length: %d\n", strings.Repeat("| ", encodeIndent), len(p.content)) - out.Write(p.content) - - return nil -} - -func ber2der(ber []byte) ([]byte, error) { - if len(ber) == 0 { - return nil, errors.New("ber2der: input ber is empty") - } - //fmt.Printf("--> ber2der: Transcoding %d bytes\n", len(ber)) - out := new(bytes.Buffer) - - obj, _, err := readObject(ber, 0) - if err != nil { - return nil, err - } - obj.EncodeTo(out) - - // if offset < len(ber) { - // return nil, fmt.Errorf("ber2der: Content longer than expected. Got %d, expected %d", offset, len(ber)) - //} - - return out.Bytes(), nil -} - -// encodes lengths that are longer than 127 into string of bytes -func marshalLongLength(out *bytes.Buffer, i int) (err error) { - n := lengthLength(i) - - for ; n > 0; n-- { - err = out.WriteByte(byte(i >> uint((n-1)*8))) - if err != nil { - return - } - } - - return nil -} - -// computes the byte length of an encoded length value -func lengthLength(i int) (numBytes int) { - numBytes = 1 - for i > 255 { - numBytes++ - i >>= 8 - } - return -} - -// encodes the length in DER format -// If the length fits in 7 bits, the value is encoded directly. -// -// Otherwise, the number of bytes to encode the length is first determined. -// This number is likely to be 4 or less for a 32bit length. This number is -// added to 0x80. The length is encoded in big endian encoding follow after -// -// Examples: -// length | byte 1 | bytes n -// 0 | 0x00 | - -// 120 | 0x78 | - -// 200 | 0x81 | 0xC8 -// 500 | 0x82 | 0x01 0xF4 -// -func encodeLength(out *bytes.Buffer, length int) (err error) { - if length >= 128 { - l := lengthLength(length) - err = out.WriteByte(0x80 | byte(l)) - if err != nil { - return - } - err = marshalLongLength(out, length) - if err != nil { - return - } - } else { - err = out.WriteByte(byte(length)) - if err != nil { - return - } - } - return -} - -func readObject(ber []byte, offset int) (asn1Object, int, error) { - berLen := len(ber) - if offset >= berLen { - return nil, 0, errors.New("ber2der: offset is after end of ber data") - } - tagStart := offset - b := ber[offset] - offset++ - if offset >= berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - tag := b & 0x1F // last 5 bits - if tag == 0x1F { - tag = 0 - for ber[offset] >= 0x80 { - tag = tag*128 + ber[offset] - 0x80 - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - // jvehent 20170227: this doesn't appear to be used anywhere... - //tag = tag*128 + ber[offset] - 0x80 - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - tagEnd := offset - - kind := b & 0x20 - if kind == 0 { - debugprint("--> Primitive\n") - } else { - debugprint("--> Constructed\n") - } - // read length - var length int - l := ber[offset] - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - hack := 0 - if l > 0x80 { - numberOfBytes := (int)(l & 0x7F) - if numberOfBytes > 4 { // int is only guaranteed to be 32bit - return nil, 0, errors.New("ber2der: BER tag length too long") - } - if numberOfBytes == 4 && (int)(ber[offset]) > 0x7F { - return nil, 0, errors.New("ber2der: BER tag length is negative") - } - if (int)(ber[offset]) == 0x0 { - return nil, 0, errors.New("ber2der: BER tag length has leading zero") - } - debugprint("--> (compute length) indicator byte: %x\n", l) - debugprint("--> (compute length) length bytes: % X\n", ber[offset:offset+numberOfBytes]) - for i := 0; i < numberOfBytes; i++ { - length = length*256 + (int)(ber[offset]) - offset++ - if offset > berLen { - return nil, 0, errors.New("ber2der: cannot move offset forward, end of ber data reached") - } - } - } else if l == 0x80 { - // find length by searching content - markerIndex := bytes.LastIndex(ber[offset:], []byte{0x0, 0x0}) - if markerIndex == -1 { - return nil, 0, errors.New("ber2der: Invalid BER format") - } - length = markerIndex - hack = 2 - debugprint("--> (compute length) marker found at offset: %d\n", markerIndex+offset) - } else { - length = (int)(l) - } - if length < 0 { - return nil, 0, errors.New("ber2der: invalid negative value found in BER tag length") - } - //fmt.Printf("--> length : %d\n", length) - contentEnd := offset + length - if contentEnd > len(ber) { - return nil, 0, errors.New("ber2der: BER tag length is more than available data") - } - debugprint("--> content start : %d\n", offset) - debugprint("--> content end : %d\n", contentEnd) - debugprint("--> content : % X\n", ber[offset:contentEnd]) - var obj asn1Object - if kind == 0 { - obj = asn1Primitive{ - tagBytes: ber[tagStart:tagEnd], - length: length, - content: ber[offset:contentEnd], - } - } else { - var subObjects []asn1Object - for offset < contentEnd { - var subObj asn1Object - var err error - subObj, offset, err = readObject(ber[:contentEnd], offset) - if err != nil { - return nil, 0, err - } - subObjects = append(subObjects, subObj) - } - obj = asn1Structured{ - tagBytes: ber[tagStart:tagEnd], - content: subObjects, - } - } - - return obj, contentEnd + hack, nil -} - -func debugprint(format string, a ...interface{}) { - //fmt.Printf(format, a) -} diff --git a/scep/pkcs7/ber_test.go b/scep/pkcs7/ber_test.go deleted file mode 100644 index fcb4b6a2..00000000 --- a/scep/pkcs7/ber_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "encoding/asn1" - "strings" - "testing" -) - -func TestBer2Der(t *testing.T) { - // indefinite length fixture - ber := []byte{0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0x00} - expected := []byte{0x30, 0x03, 0x02, 0x01, 0x01} - der, err := ber2der(ber) - if err != nil { - t.Fatalf("ber2der failed with error: %v", err) - } - if !bytes.Equal(der, expected) { - t.Errorf("ber2der result did not match.\n\tExpected: % X\n\tActual: % X", expected, der) - } - - if der2, err := ber2der(der); err != nil { - t.Errorf("ber2der on DER bytes failed with error: %v", err) - } else { - if !bytes.Equal(der, der2) { - t.Error("ber2der is not idempotent") - } - } - var thing struct { - Number int - } - rest, err := asn1.Unmarshal(der, &thing) - if err != nil { - t.Errorf("Cannot parse resulting DER because: %v", err) - } else if len(rest) > 0 { - t.Errorf("Resulting DER has trailing data: % X", rest) - } -} - -func TestBer2Der_Negatives(t *testing.T) { - fixtures := []struct { - Input []byte - ErrorContains string - }{ - {[]byte{0x30, 0x85}, "tag length too long"}, - {[]byte{0x30, 0x84, 0x80, 0x0, 0x0, 0x0}, "length is negative"}, - {[]byte{0x30, 0x82, 0x0, 0x1}, "length has leading zero"}, - {[]byte{0x30, 0x80, 0x1, 0x2}, "Invalid BER format"}, - {[]byte{0x30, 0x03, 0x01, 0x02}, "length is more than available data"}, - {[]byte{0x30}, "end of ber data reached"}, - } - - for _, fixture := range fixtures { - _, err := ber2der(fixture.Input) - if err == nil { - t.Errorf("No error thrown. Expected: %s", fixture.ErrorContains) - } - if !strings.Contains(err.Error(), fixture.ErrorContains) { - t.Errorf("Unexpected error thrown.\n\tExpected: /%s/\n\tActual: %s", fixture.ErrorContains, err.Error()) - } - } -} diff --git a/scep/pkcs7/decrypt.go b/scep/pkcs7/decrypt.go deleted file mode 100644 index 0d088d62..00000000 --- a/scep/pkcs7/decrypt.go +++ /dev/null @@ -1,177 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/asn1" - "errors" - "fmt" -) - -// ErrUnsupportedAlgorithm tells you when our quick dev assumptions have failed -var ErrUnsupportedAlgorithm = errors.New("pkcs7: cannot decrypt data: only RSA, DES, DES-EDE3, AES-256-CBC and AES-128-GCM supported") - -// ErrNotEncryptedContent is returned when attempting to Decrypt data that is not encrypted data -var ErrNotEncryptedContent = errors.New("pkcs7: content data is a decryptable data type") - -// Decrypt decrypts encrypted content info for recipient cert and private key -func (p7 *PKCS7) Decrypt(cert *x509.Certificate, pkey crypto.PrivateKey) ([]byte, error) { - data, ok := p7.raw.(envelopedData) - if !ok { - return nil, ErrNotEncryptedContent - } - recipient := selectRecipientForCertificate(data.RecipientInfos, cert) - if recipient.EncryptedKey == nil { - return nil, errors.New("pkcs7: no enveloped recipient for provided certificate") - } - switch pkey := pkey.(type) { - case *rsa.PrivateKey: - var contentKey []byte - contentKey, err := rsa.DecryptPKCS1v15(rand.Reader, pkey, recipient.EncryptedKey) - if err != nil { - return nil, err - } - return data.EncryptedContentInfo.decrypt(contentKey) - } - return nil, ErrUnsupportedAlgorithm -} - -// DecryptUsingPSK decrypts encrypted data using caller provided -// pre-shared secret -func (p7 *PKCS7) DecryptUsingPSK(key []byte) ([]byte, error) { - data, ok := p7.raw.(encryptedData) - if !ok { - return nil, ErrNotEncryptedContent - } - return data.EncryptedContentInfo.decrypt(key) -} - -func (eci encryptedContentInfo) decrypt(key []byte) ([]byte, error) { - alg := eci.ContentEncryptionAlgorithm.Algorithm - if !alg.Equal(OIDEncryptionAlgorithmDESCBC) && - !alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES256CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES128CBC) && - !alg.Equal(OIDEncryptionAlgorithmAES128GCM) && - !alg.Equal(OIDEncryptionAlgorithmAES256GCM) { - fmt.Printf("Unsupported Content Encryption Algorithm: %s\n", alg) - return nil, ErrUnsupportedAlgorithm - } - - // EncryptedContent can either be constructed of multple OCTET STRINGs - // or _be_ a tagged OCTET STRING - var cyphertext []byte - if eci.EncryptedContent.IsCompound { - // Complex case to concat all of the children OCTET STRINGs - var buf bytes.Buffer - cypherbytes := eci.EncryptedContent.Bytes - for { - var part []byte - cypherbytes, _ = asn1.Unmarshal(cypherbytes, &part) - buf.Write(part) - if cypherbytes == nil { - break - } - } - cyphertext = buf.Bytes() - } else { - // Simple case, the bytes _are_ the cyphertext - cyphertext = eci.EncryptedContent.Bytes - } - - var block cipher.Block - var err error - - switch { - case alg.Equal(OIDEncryptionAlgorithmDESCBC): - block, err = des.NewCipher(key) - case alg.Equal(OIDEncryptionAlgorithmDESEDE3CBC): - block, err = des.NewTripleDESCipher(key) - case alg.Equal(OIDEncryptionAlgorithmAES256CBC), alg.Equal(OIDEncryptionAlgorithmAES256GCM): - fallthrough - case alg.Equal(OIDEncryptionAlgorithmAES128GCM), alg.Equal(OIDEncryptionAlgorithmAES128CBC): - block, err = aes.NewCipher(key) - } - - if err != nil { - return nil, err - } - - if alg.Equal(OIDEncryptionAlgorithmAES128GCM) || alg.Equal(OIDEncryptionAlgorithmAES256GCM) { - params := aesGCMParameters{} - paramBytes := eci.ContentEncryptionAlgorithm.Parameters.Bytes - - _, err := asn1.Unmarshal(paramBytes, ¶ms) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - if len(params.Nonce) != gcm.NonceSize() { - return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") - } - if params.ICVLen != gcm.Overhead() { - return nil, errors.New("pkcs7: encryption algorithm parameters are incorrect") - } - - plaintext, err := gcm.Open(nil, params.Nonce, cyphertext, nil) - if err != nil { - return nil, err - } - - return plaintext, nil - } - - iv := eci.ContentEncryptionAlgorithm.Parameters.Bytes - if len(iv) != block.BlockSize() { - return nil, errors.New("pkcs7: encryption algorithm parameters are malformed") - } - mode := cipher.NewCBCDecrypter(block, iv) - plaintext := make([]byte, len(cyphertext)) - mode.CryptBlocks(plaintext, cyphertext) - if plaintext, err = unpad(plaintext, mode.BlockSize()); err != nil { - return nil, err - } - return plaintext, nil -} - -func unpad(data []byte, blocklen int) ([]byte, error) { - if blocklen < 1 { - return nil, fmt.Errorf("invalid blocklen %d", blocklen) - } - if len(data)%blocklen != 0 || len(data) == 0 { - return nil, fmt.Errorf("invalid data len %d", len(data)) - } - - // the last byte is the length of padding - padlen := int(data[len(data)-1]) - - // check padding integrity, all bytes should be the same - pad := data[len(data)-padlen:] - for _, padbyte := range pad { - if padbyte != byte(padlen) { - return nil, errors.New("invalid padding") - } - } - - return data[:len(data)-padlen], nil -} - -func selectRecipientForCertificate(recipients []recipientInfo, cert *x509.Certificate) recipientInfo { - for _, recp := range recipients { - if isCertMatchForIssuerAndSerial(cert, recp.IssuerAndSerialNumber) { - return recp - } - } - return recipientInfo{} -} diff --git a/scep/pkcs7/decrypt_test.go b/scep/pkcs7/decrypt_test.go deleted file mode 100644 index 06cc0f80..00000000 --- a/scep/pkcs7/decrypt_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "testing" -) - -func TestDecrypt(t *testing.T) { - fixture := UnmarshalTestFixture(EncryptedTestFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Fatal(err) - } - content, err := p7.Decrypt(fixture.Certificate, fixture.PrivateKey) - if err != nil { - t.Errorf("Cannot Decrypt with error: %v", err) - } - expected := []byte("This is a test") - if !bytes.Equal(content, expected) { - t.Errorf("Decrypted result does not match.\n\tExpected:%s\n\tActual:%s", expected, content) - } -} - -// Content is "This is a test" -var EncryptedTestFixture = ` ------BEGIN PKCS7----- -MIIBFwYJKoZIhvcNAQcDoIIBCDCCAQQCAQAxgcowgccCAQAwMjApMRAwDgYDVQQK -EwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3RhcmsCBQDL+CvWMAsGCSqGSIb3 -DQEBAQSBgKyP/5WlRTZD3dWMrLOX6QRNDrXEkQjhmToRwFZdY3LgUh25ZU0S/q4G -dHPV21Fv9lQD+q7l3vfeHw8M6Z1PKi9sHMVfxAkQpvaI96DTIT3YHtuLC1w3geCO -8eFWTq2qS4WChSuS/yhYosjA1kTkE0eLnVZcGw0z/WVuEZznkdyIMDIGCSqGSIb3 -DQEHATARBgUrDgMCBwQImpKsUyMPpQigEgQQRcWWrCRXqpD5Njs0GkJl+g== ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIB1jCCAUGgAwIBAgIFAMv4K9YwCwYJKoZIhvcNAQELMCkxEDAOBgNVBAoTB0Fj -bWUgQ28xFTATBgNVBAMTDEVkZGFyZCBTdGFyazAeFw0xNTA1MDYwMzU2NDBaFw0x -NjA1MDYwMzU2NDBaMCUxEDAOBgNVBAoTB0FjbWUgQ28xETAPBgNVBAMTCEpvbiBT -bm93MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK6NU0R0eiCYVquU4RcjKc -LzGfx0aa1lMr2TnLQUSeLFZHFxsyyMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg -8+Zg2r8xnnney7abxcuv0uATWSIeKlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP -+Zxp2ni5qHNraf3wE2VPIQIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCAKAwCwYJKoZI -hvcNAQELA4GBAIr2F7wsqmEU/J/kLyrCgEVXgaV/sKZq4pPNnzS0tBYk8fkV3V18 -sBJyHKRLL/wFZASvzDcVGCplXyMdAOCyfd8jO3F9Ac/xdlz10RrHJT75hNu3a7/n -9KNwKhfN4A1CQv2x372oGjRhCW5bHNCWx4PIVeNzCyq/KZhyY9sxHE6f ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIICXgIBAAKBgQDK6NU0R0eiCYVquU4RcjKcLzGfx0aa1lMr2TnLQUSeLFZHFxsy -yMXXuMPig3HK4A7SGFHupO+/1H/sL4xpH5zg8+Zg2r8xnnney7abxcuv0uATWSIe -KlNnb1ZO1BAxFnESc3GtyOCr2dUwZHX5mRVP+Zxp2ni5qHNraf3wE2VPIQIDAQAB -AoGBALyvnSt7KUquDen7nXQtvJBudnf9KFPt//OjkdHHxNZNpoF/JCSqfQeoYkeu -MdAVYNLQGMiRifzZz4dDhA9xfUAuy7lcGQcMCxEQ1dwwuFaYkawbS0Tvy2PFlq2d -H5/HeDXU4EDJ3BZg0eYj2Bnkt1sJI35UKQSxblQ0MY2q0uFBAkEA5MMOogkgUx1C -67S1tFqMUSM8D0mZB0O5vOJZC5Gtt2Urju6vywge2ArExWRXlM2qGl8afFy2SgSv -Xk5eybcEiQJBAOMRwwbEoW5NYHuFFbSJyWll4n71CYuWuQOCzehDPyTb80WFZGLV -i91kFIjeERyq88eDE5xVB3ZuRiXqaShO/9kCQQCKOEkpInaDgZSjskZvuJ47kByD -6CYsO4GIXQMMeHML8ncFH7bb6AYq5ybJVb2NTU7QLFJmfeYuhvIm+xdOreRxAkEA -o5FC5Jg2FUfFzZSDmyZ6IONUsdF/i78KDV5nRv1R+hI6/oRlWNCtTNBv/lvBBd6b -dseUE9QoaQZsn5lpILEvmQJAZ0B+Or1rAYjnbjnUhdVZoy9kC4Zov+4UH3N/BtSy -KJRWUR0wTWfZBPZ5hAYZjTBEAFULaYCXlQKsODSp0M1aQA== ------END PRIVATE KEY-----` diff --git a/scep/pkcs7/encrypt.go b/scep/pkcs7/encrypt.go deleted file mode 100644 index da57ae64..00000000 --- a/scep/pkcs7/encrypt.go +++ /dev/null @@ -1,399 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/des" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" -) - -type envelopedData struct { - Version int - RecipientInfos []recipientInfo `asn1:"set"` - EncryptedContentInfo encryptedContentInfo -} - -type encryptedData struct { - Version int - EncryptedContentInfo encryptedContentInfo -} - -type recipientInfo struct { - Version int - IssuerAndSerialNumber issuerAndSerial - KeyEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedKey []byte -} - -type encryptedContentInfo struct { - ContentType asn1.ObjectIdentifier - ContentEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedContent asn1.RawValue `asn1:"tag:0,optional,explicit"` -} - -const ( - // EncryptionAlgorithmDESCBC is the DES CBC encryption algorithm - EncryptionAlgorithmDESCBC = iota - - // EncryptionAlgorithmAES128CBC is the AES 128 bits with CBC encryption algorithm - // Avoid this algorithm unless required for interoperability; use AES GCM instead. - EncryptionAlgorithmAES128CBC - - // EncryptionAlgorithmAES256CBC is the AES 256 bits with CBC encryption algorithm - // Avoid this algorithm unless required for interoperability; use AES GCM instead. - EncryptionAlgorithmAES256CBC - - // EncryptionAlgorithmAES128GCM is the AES 128 bits with GCM encryption algorithm - EncryptionAlgorithmAES128GCM - - // EncryptionAlgorithmAES256GCM is the AES 256 bits with GCM encryption algorithm - EncryptionAlgorithmAES256GCM -) - -// ContentEncryptionAlgorithm determines the algorithm used to encrypt the -// plaintext message. Change the value of this variable to change which -// algorithm is used in the Encrypt() function. -var ContentEncryptionAlgorithm = EncryptionAlgorithmDESCBC - -// ErrUnsupportedEncryptionAlgorithm is returned when attempting to encrypt -// content with an unsupported algorithm. -var ErrUnsupportedEncryptionAlgorithm = errors.New("pkcs7: cannot encrypt content: only DES-CBC, AES-CBC, and AES-GCM supported") - -// ErrPSKNotProvided is returned when attempting to encrypt -// using a PSK without actually providing the PSK. -var ErrPSKNotProvided = errors.New("pkcs7: cannot encrypt content: PSK not provided") - -const nonceSize = 12 - -type aesGCMParameters struct { - Nonce []byte `asn1:"tag:4"` - ICVLen int -} - -func encryptAESGCM(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - var keyLen int - var algID asn1.ObjectIdentifier - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmAES128GCM: - keyLen = 16 - algID = OIDEncryptionAlgorithmAES128GCM - case EncryptionAlgorithmAES256GCM: - keyLen = 32 - algID = OIDEncryptionAlgorithmAES256GCM - default: - return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESGCM: %d", ContentEncryptionAlgorithm) - } - if key == nil { - // Create AES key - key = make([]byte, keyLen) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create nonce - nonce := make([]byte, nonceSize) - - _, err := rand.Read(nonce) - if err != nil { - return nil, nil, err - } - - // Encrypt content - block, err := aes.NewCipher(key) - if err != nil { - return nil, nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, nil, err - } - - ciphertext := gcm.Seal(nil, nonce, content, nil) - - // Prepare ASN.1 Encrypted Content Info - paramSeq := aesGCMParameters{ - Nonce: nonce, - ICVLen: gcm.Overhead(), - } - - paramBytes, err := asn1.Marshal(paramSeq) - if err != nil { - return nil, nil, err - } - - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: algID, - Parameters: asn1.RawValue{ - Tag: asn1.TagSequence, - Bytes: paramBytes, - }, - }, - EncryptedContent: marshalEncryptedContent(ciphertext), - } - - return key, &eci, nil -} - -func encryptDESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - if key == nil { - // Create DES key - key = make([]byte, 8) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create CBC IV - iv := make([]byte, des.BlockSize) - _, err := rand.Read(iv) - if err != nil { - return nil, nil, err - } - - // Encrypt padded content - block, err := des.NewCipher(key) - if err != nil { - return nil, nil, err - } - mode := cipher.NewCBCEncrypter(block, iv) - plaintext, err := pad(content, mode.BlockSize()) - if err != nil { - return nil, nil, err - } - cyphertext := make([]byte, len(plaintext)) - mode.CryptBlocks(cyphertext, plaintext) - - // Prepare ASN.1 Encrypted Content Info - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: OIDEncryptionAlgorithmDESCBC, - Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, - }, - EncryptedContent: marshalEncryptedContent(cyphertext), - } - - return key, &eci, nil -} - -func encryptAESCBC(content []byte, key []byte) ([]byte, *encryptedContentInfo, error) { - var keyLen int - var algID asn1.ObjectIdentifier - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmAES128CBC: - keyLen = 16 - algID = OIDEncryptionAlgorithmAES128CBC - case EncryptionAlgorithmAES256CBC: - keyLen = 32 - algID = OIDEncryptionAlgorithmAES256CBC - default: - return nil, nil, fmt.Errorf("invalid ContentEncryptionAlgorithm in encryptAESCBC: %d", ContentEncryptionAlgorithm) - } - - if key == nil { - // Create AES key - key = make([]byte, keyLen) - - _, err := rand.Read(key) - if err != nil { - return nil, nil, err - } - } - - // Create CBC IV - iv := make([]byte, aes.BlockSize) - _, err := rand.Read(iv) - if err != nil { - return nil, nil, err - } - - // Encrypt padded content - block, err := aes.NewCipher(key) - if err != nil { - return nil, nil, err - } - mode := cipher.NewCBCEncrypter(block, iv) - plaintext, err := pad(content, mode.BlockSize()) - if err != nil { - return nil, nil, err - } - cyphertext := make([]byte, len(plaintext)) - mode.CryptBlocks(cyphertext, plaintext) - - // Prepare ASN.1 Encrypted Content Info - eci := encryptedContentInfo{ - ContentType: OIDData, - ContentEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: algID, - Parameters: asn1.RawValue{Tag: 4, Bytes: iv}, - }, - EncryptedContent: marshalEncryptedContent(cyphertext), - } - - return key, &eci, nil -} - -// Encrypt creates and returns an envelope data PKCS7 structure with encrypted -// recipient keys for each recipient public key. -// -// The algorithm used to perform encryption is determined by the current value -// of the global ContentEncryptionAlgorithm package variable. By default, the -// value is EncryptionAlgorithmDESCBC. To use a different algorithm, change the -// value before calling Encrypt(). For example: -// -// ContentEncryptionAlgorithm = EncryptionAlgorithmAES128GCM -// -// TODO(fullsailor): Add support for encrypting content with other algorithms -func Encrypt(content []byte, recipients []*x509.Certificate) ([]byte, error) { - var eci *encryptedContentInfo - var key []byte - var err error - - // Apply chosen symmetric encryption method - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmDESCBC: - key, eci, err = encryptDESCBC(content, nil) - case EncryptionAlgorithmAES128CBC: - fallthrough - case EncryptionAlgorithmAES256CBC: - key, eci, err = encryptAESCBC(content, nil) - case EncryptionAlgorithmAES128GCM: - fallthrough - case EncryptionAlgorithmAES256GCM: - key, eci, err = encryptAESGCM(content, nil) - - default: - return nil, ErrUnsupportedEncryptionAlgorithm - } - - if err != nil { - return nil, err - } - - // Prepare each recipient's encrypted cipher key - recipientInfos := make([]recipientInfo, len(recipients)) - for i, recipient := range recipients { - encrypted, err := encryptKey(key, recipient) - if err != nil { - return nil, err - } - ias, err := cert2issuerAndSerial(recipient) - if err != nil { - return nil, err - } - info := recipientInfo{ - Version: 0, - IssuerAndSerialNumber: ias, - KeyEncryptionAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: OIDEncryptionAlgorithmRSA, - }, - EncryptedKey: encrypted, - } - recipientInfos[i] = info - } - - // Prepare envelope content - envelope := envelopedData{ - EncryptedContentInfo: *eci, - Version: 0, - RecipientInfos: recipientInfos, - } - innerContent, err := asn1.Marshal(envelope) - if err != nil { - return nil, err - } - - // Prepare outer payload structure - wrapper := contentInfo{ - ContentType: OIDEnvelopedData, - Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, - } - - return asn1.Marshal(wrapper) -} - -// EncryptUsingPSK creates and returns an encrypted data PKCS7 structure, -// encrypted using caller provided pre-shared secret. -func EncryptUsingPSK(content []byte, key []byte) ([]byte, error) { - var eci *encryptedContentInfo - var err error - - if key == nil { - return nil, ErrPSKNotProvided - } - - // Apply chosen symmetric encryption method - switch ContentEncryptionAlgorithm { - case EncryptionAlgorithmDESCBC: - _, eci, err = encryptDESCBC(content, key) - - case EncryptionAlgorithmAES128GCM: - fallthrough - case EncryptionAlgorithmAES256GCM: - _, eci, err = encryptAESGCM(content, key) - - default: - return nil, ErrUnsupportedEncryptionAlgorithm - } - - if err != nil { - return nil, err - } - - // Prepare encrypted-data content - ed := encryptedData{ - Version: 0, - EncryptedContentInfo: *eci, - } - innerContent, err := asn1.Marshal(ed) - if err != nil { - return nil, err - } - - // Prepare outer payload structure - wrapper := contentInfo{ - ContentType: OIDEncryptedData, - Content: asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: innerContent}, - } - - return asn1.Marshal(wrapper) -} - -func marshalEncryptedContent(content []byte) asn1.RawValue { - asn1Content, _ := asn1.Marshal(content) - return asn1.RawValue{Tag: 0, Class: 2, Bytes: asn1Content, IsCompound: true} -} - -func encryptKey(key []byte, recipient *x509.Certificate) ([]byte, error) { - if pub := recipient.PublicKey.(*rsa.PublicKey); pub != nil { - return rsa.EncryptPKCS1v15(rand.Reader, pub, key) - } - return nil, ErrUnsupportedAlgorithm -} - -func pad(data []byte, blocklen int) ([]byte, error) { - if blocklen < 1 { - return nil, fmt.Errorf("invalid blocklen %d", blocklen) - } - padlen := blocklen - (len(data) % blocklen) - if padlen == 0 { - padlen = blocklen - } - pad := bytes.Repeat([]byte{byte(padlen)}, padlen) - return append(data, pad...), nil -} diff --git a/scep/pkcs7/encrypt_test.go b/scep/pkcs7/encrypt_test.go deleted file mode 100644 index c64381e2..00000000 --- a/scep/pkcs7/encrypt_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/x509" - "testing" -) - -func TestEncrypt(t *testing.T) { - modes := []int{ - EncryptionAlgorithmDESCBC, - EncryptionAlgorithmAES128CBC, - EncryptionAlgorithmAES256CBC, - EncryptionAlgorithmAES128GCM, - EncryptionAlgorithmAES256GCM, - } - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - } - for _, mode := range modes { - for _, sigalg := range sigalgs { - ContentEncryptionAlgorithm = mode - - plaintext := []byte("Hello Secret World!") - cert, err := createTestCertificate(sigalg) - if err != nil { - t.Fatal(err) - } - encrypted, err := Encrypt(plaintext, []*x509.Certificate{cert.Certificate}) - if err != nil { - t.Fatal(err) - } - p7, err := Parse(encrypted) - if err != nil { - t.Fatalf("cannot Parse encrypted result: %s", err) - } - result, err := p7.Decrypt(cert.Certificate, *cert.PrivateKey) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } - } -} - -func TestEncryptUsingPSK(t *testing.T) { - modes := []int{ - EncryptionAlgorithmDESCBC, - EncryptionAlgorithmAES128GCM, - } - - for _, mode := range modes { - ContentEncryptionAlgorithm = mode - plaintext := []byte("Hello Secret World!") - var key []byte - - switch mode { - case EncryptionAlgorithmDESCBC: - key = []byte("64BitKey") - case EncryptionAlgorithmAES128GCM: - key = []byte("128BitKey4AESGCM") - } - ciphertext, err := EncryptUsingPSK(plaintext, key) - if err != nil { - t.Fatal(err) - } - - p7, _ := Parse(ciphertext) - result, err := p7.DecryptUsingPSK(key) - if err != nil { - t.Fatalf("cannot Decrypt encrypted result: %s", err) - } - if !bytes.Equal(plaintext, result) { - t.Errorf("encrypted data does not match plaintext:\n\tExpected: %s\n\tActual: %s", plaintext, result) - } - } -} - -func TestPad(t *testing.T) { - tests := []struct { - Original []byte - Expected []byte - BlockSize int - }{ - {[]byte{0x1, 0x2, 0x3, 0x10}, []byte{0x1, 0x2, 0x3, 0x10, 0x4, 0x4, 0x4, 0x4}, 8}, - {[]byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0}, []byte{0x1, 0x2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8}, 8}, - } - for _, test := range tests { - padded, err := pad(test.Original, test.BlockSize) - if err != nil { - t.Errorf("pad encountered error: %s", err) - continue - } - if !bytes.Equal(test.Expected, padded) { - t.Errorf("pad results mismatch:\n\tExpected: %X\n\tActual: %X", test.Expected, padded) - } - } -} diff --git a/scep/pkcs7/pkcs7.go b/scep/pkcs7/pkcs7.go deleted file mode 100644 index ccc6cc6d..00000000 --- a/scep/pkcs7/pkcs7.go +++ /dev/null @@ -1,291 +0,0 @@ -// Package pkcs7 implements parsing and generation of some PKCS#7 structures. -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "sort" - - _ "crypto/sha1" // for crypto.SHA1 -) - -// PKCS7 Represents a PKCS7 structure -type PKCS7 struct { - Content []byte - Certificates []*x509.Certificate - CRLs []pkix.CertificateList - Signers []signerInfo - raw interface{} -} - -type contentInfo struct { - ContentType asn1.ObjectIdentifier - Content asn1.RawValue `asn1:"explicit,optional,tag:0"` -} - -// ErrUnsupportedContentType is returned when a PKCS7 content is not supported. -// Currently only Data (1.2.840.113549.1.7.1), Signed Data (1.2.840.113549.1.7.2), -// and Enveloped Data are supported (1.2.840.113549.1.7.3) -var ErrUnsupportedContentType = errors.New("pkcs7: cannot parse data: unimplemented content type") - -type unsignedData []byte - -var ( - // Signed Data OIDs - OIDData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} - OIDSignedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 2} - OIDEnvelopedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 3} - OIDEncryptedData = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 6} - OIDAttributeContentType = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 3} - OIDAttributeMessageDigest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 4} - OIDAttributeSigningTime = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 5} - - // Digest Algorithms - OIDDigestAlgorithmSHA1 = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 26} - OIDDigestAlgorithmSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} - OIDDigestAlgorithmSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} - OIDDigestAlgorithmSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} - - OIDDigestAlgorithmDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} - OIDDigestAlgorithmDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} - - OIDDigestAlgorithmECDSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} - OIDDigestAlgorithmECDSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} - OIDDigestAlgorithmECDSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} - OIDDigestAlgorithmECDSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} - - // Signature Algorithms - OIDEncryptionAlgorithmRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} - OIDEncryptionAlgorithmRSASHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} - OIDEncryptionAlgorithmRSASHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} - OIDEncryptionAlgorithmRSASHA384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} - OIDEncryptionAlgorithmRSASHA512 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} - - OIDEncryptionAlgorithmECDSAP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} - OIDEncryptionAlgorithmECDSAP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} - OIDEncryptionAlgorithmECDSAP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} - - // Encryption Algorithms - OIDEncryptionAlgorithmDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} - OIDEncryptionAlgorithmDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} - OIDEncryptionAlgorithmAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} - OIDEncryptionAlgorithmAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} - OIDEncryptionAlgorithmAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} - OIDEncryptionAlgorithmAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46} -) - -func getHashForOID(oid asn1.ObjectIdentifier) (crypto.Hash, error) { - switch { - case oid.Equal(OIDDigestAlgorithmSHA1), oid.Equal(OIDDigestAlgorithmECDSASHA1), - oid.Equal(OIDDigestAlgorithmDSA), oid.Equal(OIDDigestAlgorithmDSASHA1), - oid.Equal(OIDEncryptionAlgorithmRSA): - return crypto.SHA1, nil - case oid.Equal(OIDDigestAlgorithmSHA256), oid.Equal(OIDDigestAlgorithmECDSASHA256): - return crypto.SHA256, nil - case oid.Equal(OIDDigestAlgorithmSHA384), oid.Equal(OIDDigestAlgorithmECDSASHA384): - return crypto.SHA384, nil - case oid.Equal(OIDDigestAlgorithmSHA512), oid.Equal(OIDDigestAlgorithmECDSASHA512): - return crypto.SHA512, nil - } - return crypto.Hash(0), ErrUnsupportedAlgorithm -} - -// getDigestOIDForSignatureAlgorithm takes an x509.SignatureAlgorithm -// and returns the corresponding OID digest algorithm -func getDigestOIDForSignatureAlgorithm(digestAlg x509.SignatureAlgorithm) (asn1.ObjectIdentifier, error) { - switch digestAlg { - case x509.SHA1WithRSA, x509.ECDSAWithSHA1: - return OIDDigestAlgorithmSHA1, nil - case x509.SHA256WithRSA, x509.ECDSAWithSHA256: - return OIDDigestAlgorithmSHA256, nil - case x509.SHA384WithRSA, x509.ECDSAWithSHA384: - return OIDDigestAlgorithmSHA384, nil - case x509.SHA512WithRSA, x509.ECDSAWithSHA512: - return OIDDigestAlgorithmSHA512, nil - } - return nil, fmt.Errorf("pkcs7: cannot convert hash to oid, unknown hash algorithm") -} - -// getOIDForEncryptionAlgorithm takes the private key type of the signer and -// the OID of a digest algorithm to return the appropriate signerInfo.DigestEncryptionAlgorithm -func getOIDForEncryptionAlgorithm(pkey crypto.PrivateKey, OIDDigestAlg asn1.ObjectIdentifier) (asn1.ObjectIdentifier, error) { - switch pkey.(type) { - case *rsa.PrivateKey: - switch { - default: - return OIDEncryptionAlgorithmRSA, nil - case OIDDigestAlg.Equal(OIDEncryptionAlgorithmRSA): - return OIDEncryptionAlgorithmRSA, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): - return OIDEncryptionAlgorithmRSASHA1, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): - return OIDEncryptionAlgorithmRSASHA256, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): - return OIDEncryptionAlgorithmRSASHA384, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): - return OIDEncryptionAlgorithmRSASHA512, nil - } - case *ecdsa.PrivateKey: - switch { - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA1): - return OIDDigestAlgorithmECDSASHA1, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA256): - return OIDDigestAlgorithmECDSASHA256, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA384): - return OIDDigestAlgorithmECDSASHA384, nil - case OIDDigestAlg.Equal(OIDDigestAlgorithmSHA512): - return OIDDigestAlgorithmECDSASHA512, nil - } - case *dsa.PrivateKey: - return OIDDigestAlgorithmDSA, nil - } - return nil, fmt.Errorf("pkcs7: cannot convert encryption algorithm to oid, unknown private key type %T", pkey) - -} - -// Parse decodes a DER encoded PKCS7 package -func Parse(data []byte) (p7 *PKCS7, err error) { - if len(data) == 0 { - return nil, errors.New("pkcs7: input data is empty") - } - var info contentInfo - der, err := ber2der(data) - if err != nil { - return nil, err - } - rest, err := asn1.Unmarshal(der, &info) - if len(rest) > 0 { - err = asn1.SyntaxError{Msg: "trailing data"} - return - } - if err != nil { - return - } - - // fmt.Printf("--> Content Type: %s", info.ContentType) - switch { - case info.ContentType.Equal(OIDSignedData): - return parseSignedData(info.Content.Bytes) - case info.ContentType.Equal(OIDEnvelopedData): - return parseEnvelopedData(info.Content.Bytes) - case info.ContentType.Equal(OIDEncryptedData): - return parseEncryptedData(info.Content.Bytes) - } - return nil, ErrUnsupportedContentType -} - -func parseEnvelopedData(data []byte) (*PKCS7, error) { - var ed envelopedData - if _, err := asn1.Unmarshal(data, &ed); err != nil { - return nil, err - } - return &PKCS7{ - raw: ed, - }, nil -} - -func parseEncryptedData(data []byte) (*PKCS7, error) { - var ed encryptedData - if _, err := asn1.Unmarshal(data, &ed); err != nil { - return nil, err - } - return &PKCS7{ - raw: ed, - }, nil -} - -func (raw rawCertificates) Parse() ([]*x509.Certificate, error) { - if len(raw.Raw) == 0 { - return nil, nil - } - - var val asn1.RawValue - if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { - return nil, err - } - - return x509.ParseCertificates(val.Bytes) -} - -func isCertMatchForIssuerAndSerial(cert *x509.Certificate, ias issuerAndSerial) bool { - return cert.SerialNumber.Cmp(ias.SerialNumber) == 0 && bytes.Equal(cert.RawIssuer, ias.IssuerName.FullBytes) -} - -// Attribute represents a key value pair attribute. Value must be marshalable byte -// `encoding/asn1` -type Attribute struct { - Type asn1.ObjectIdentifier - Value interface{} -} - -type attributes struct { - types []asn1.ObjectIdentifier - values []interface{} -} - -// Add adds the attribute, maintaining insertion order -func (attrs *attributes) Add(attrType asn1.ObjectIdentifier, value interface{}) { - attrs.types = append(attrs.types, attrType) - attrs.values = append(attrs.values, value) -} - -type sortableAttribute struct { - SortKey []byte - Attribute attribute -} - -type attributeSet []sortableAttribute - -func (sa attributeSet) Len() int { - return len(sa) -} - -func (sa attributeSet) Less(i, j int) bool { - return bytes.Compare(sa[i].SortKey, sa[j].SortKey) < 0 -} - -func (sa attributeSet) Swap(i, j int) { - sa[i], sa[j] = sa[j], sa[i] -} - -func (sa attributeSet) Attributes() []attribute { - attrs := make([]attribute, len(sa)) - for i, attr := range sa { - attrs[i] = attr.Attribute - } - return attrs -} - -func (attrs *attributes) ForMarshalling() ([]attribute, error) { - sortables := make(attributeSet, len(attrs.types)) - for i := range sortables { - attrType := attrs.types[i] - attrValue := attrs.values[i] - asn1Value, err := asn1.Marshal(attrValue) - if err != nil { - return nil, err - } - attr := attribute{ - Type: attrType, - Value: asn1.RawValue{Tag: 17, IsCompound: true, Bytes: asn1Value}, // 17 == SET tag - } - encoded, err := asn1.Marshal(attr) - if err != nil { - return nil, err - } - sortables[i] = sortableAttribute{ - SortKey: encoded, - Attribute: attr, - } - } - sort.Sort(sortables) - return sortables.Attributes(), nil -} diff --git a/scep/pkcs7/pkcs7_test.go b/scep/pkcs7/pkcs7_test.go deleted file mode 100644 index e743192d..00000000 --- a/scep/pkcs7/pkcs7_test.go +++ /dev/null @@ -1,326 +0,0 @@ -package pkcs7 - -import ( - "crypto" - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "log" - "math/big" - "os" - "time" -) - -var test1024Key, test2048Key, test3072Key, test4096Key *rsa.PrivateKey - -func init() { - test1024Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("123024078101403810516614073341068864574068590522569345017786163424062310013967742924377390210586226651760719671658568413826602264886073432535341149584680111145880576802262550990305759285883150470245429547886689754596541046564560506544976611114898883158121012232676781340602508151730773214407220733898059285561"), - E: 65537, - }, - D: fromBase10("118892427340746627750435157989073921703209000249285930635312944544706203626114423392257295670807166199489096863209592887347935991101581502404113203993092422730000157893515953622392722273095289787303943046491132467130346663160540744582438810535626328230098940583296878135092036661410664695896115177534496784545"), - Primes: []*big.Int{ - fromBase10("12172745919282672373981903347443034348576729562395784527365032103134165674508405592530417723266847908118361582847315228810176708212888860333051929276459099"), - fromBase10("10106518193772789699356660087736308350857919389391620140340519320928952625438936098550728858345355053201610649202713962702543058578827268756755006576249339"), - }, - } - test1024Key.Precompute() - test2048Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557"), - E: 3, - }, - D: fromBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731"), - Primes: []*big.Int{ - fromBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433"), - fromBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029"), - }, - } - test2048Key.Precompute() - test3072Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("4799422180968749215324244710281712119910779465109490663934897082847293004098645365195947978124390029272750644394844443980065532911010718425428791498896288210928474905407341584968381379157418577471272697781778686372450913810019702928839200328075568223462554606149618941566459398862673532997592879359280754226882565483298027678735544377401276021471356093819491755877827249763065753555051973844057308627201762456191918852016986546071426986328720794061622370410645440235373576002278045257207695462423797272017386006110722769072206022723167102083033531426777518054025826800254337147514768377949097720074878744769255210076910190151785807232805749219196645305822228090875616900385866236956058984170647782567907618713309775105943700661530312800231153745705977436176908325539234432407050398510090070342851489496464612052853185583222422124535243967989533830816012180864309784486694786581956050902756173889941244024888811572094961378021"), - E: 65537, - }, - D: fromBase10("4068124900056380177006532461065648259352178312499768312132802353620854992915205894105621345694615110794369150964768050224096623567443679436821868510233726084582567244003894477723706516831312989564775159596496449435830457803384416702014837685962523313266832032687145914871879794104404800823188153886925022171560391765913739346955738372354826804228989767120353182641396181570533678315099748218734875742705419933837638038793286534641711407564379950728858267828581787483317040753987167237461567332386718574803231955771633274184646232632371006762852623964054645811527580417392163873708539175349637050049959954373319861427407953413018816604365474462455009323937599275324390953644555294418021286807661559165324810415569396577697316798600308544755741549699523972971375304826663847015905713096287495342701286542193782001358775773848824496321550110946106870685499577993864871847542645561943034990484973293461948058147956373115641615329"), - Primes: []*big.Int{ - fromBase10("2378529069722721185825622840841310902793949682948530343491428052737890236476884657507685118578733560141370511507721598189068683665232991988491561624429938984370132428230072355214627085652359350722926394699707232921674771664421591347888367477300909202851476404132163673865768760147403525700174918450753162242834161458300343282159799476695001920226357456953682236859505243928716782707623075239350380352265954107362618991716602898266999700316937680986690964564264877"), - fromBase10("2017811025336026464312837780072272578817919741496395062543647660689775637351085991504709917848745137013798005682591633910555599626950744674459976829106750083386168859581016361317479081273480343110649405858059581933773354781034946787147300862495438979895430001323443224335618577322449133208754541656374335100929456885995320929464029817626916719434010943205170760536768893924932021302887114400922813817969176636993508191950649313115712159241971065134077636674146073"), - }, - } - test3072Key.Precompute() - test4096Key = &rsa.PrivateKey{ - PublicKey: rsa.PublicKey{ - N: fromBase10("633335480064287130853997429184971616419051348693342219741748040433588285601270210251206421401040394238592139790962887290698043839174341843721930134010306454716566698330215646704263665452264344664385995704186692432827662862845900348526672531755932642433662686500295989783595767573119607065791980381547677840410600100715146047382485989885183858757974681241303484641390718944520330953604501686666386926996348457928415093305041429178744778762826377713889019740060910363468343855830206640274442887621960581569183233822878661711798998132931623726434336448716605363514220760343097572198620479297583609779817750646169845195672483600293522186340560792255595411601450766002877850696008003794520089358819042318331840490155176019070646738739580486357084733208876620846449161909966690602374519398451042362690200166144326179405976024265116931974936425064291406950542193873313447617169603706868220189295654943247311295475722243471700112334609817776430552541319671117235957754556272646031356496763094955985615723596562217985372503002989591679252640940571608314743271809251568670314461039035793703429977801961867815257832671786542212589906513979094156334941265621017752516999186481477500481433634914622735206243841674973785078408289183000133399026553"), - E: 65537, - }, - D: fromBase10("439373650557744155078930178606343279553665694488479749802070836418412881168612407941793966086633543867614175621952769177088930851151267623886678906158545451731745754402575409204816390946376103491325109185445659065122640946673660760274557781540431107937331701243915001777636528502669576801704352961341634812275635811512806966908648671988644114352046582195051714797831307925775689566757438907578527366568747104508496278929566712224252103563340770696548181508180254674236716995730292431858611476396845443056967589437890065663497768422598977743046882539288481002449571403783500529740184608873520856954837631427724158592309018382711485601884461168736465751756282510065053161144027097169985941910909130083273691945578478173708396726266170473745329617793866669307716920992380350270584929908460462802627239204245339385636926433446418108504614031393494119344916828744888432279343816084433424594432427362258172264834429525166677273382617457205387388293888430391895615438030066428745187333897518037597413369705720436392869403948934993623418405908467147848576977008003556716087129242155836114780890054057743164411952731290520995017097151300091841286806603044227906213832083363876549637037625314539090155417589796428888619937329669464810549362433"), - Primes: []*big.Int{ - fromBase10("25745433817240673759910623230144796182285844101796353869339294232644316274580053211056707671663014355388701931204078502829809738396303142990312095225333440050808647355535878394534263839500592870406002873182360027755750148248672968563366185348499498613479490545488025779331426515670185366021612402246813511722553210128074701620113404560399242413747318161403908617342170447610792422053460359960010544593668037305465806912471260799852789913123044326555978680190904164976511331681163576833618899773550873682147782263100803907156362439021929408298804955194748640633152519828940133338948391986823456836070708197320166146761"), - fromBase10("24599914864909676687852658457515103765368967514652318497893275892114442089314173678877914038802355565271545910572804267918959612739009937926962653912943833939518967731764560204997062096919833970670512726396663920955497151415639902788974842698619579886297871162402643104696160155894685518587660015182381685605752989716946154299190561137541792784125356553411300817844325739404126956793095254412123887617931225840421856505925283322918693259047428656823141903489964287619982295891439430302405252447010728112098326033634688757933930065610737780413018498561434074501822951716586796047404555397992425143397497639322075233073"), - }, - } - test4096Key.Precompute() -} - -func fromBase10(base10 string) *big.Int { - i, ok := new(big.Int).SetString(base10, 10) - if !ok { - panic("bad number: " + base10) - } - return i -} - -type certKeyPair struct { - Certificate *x509.Certificate - PrivateKey *crypto.PrivateKey -} - -func createTestCertificate(sigAlg x509.SignatureAlgorithm) (certKeyPair, error) { - signer, err := createTestCertificateByIssuer("Eddard Stark", nil, sigAlg, true) - if err != nil { - return certKeyPair{}, err - } - pair, err := createTestCertificateByIssuer("Jon Snow", signer, sigAlg, false) - if err != nil { - return certKeyPair{}, err - } - return *pair, nil -} - -func createTestCertificateByIssuer(name string, issuer *certKeyPair, sigAlg x509.SignatureAlgorithm, isCA bool) (*certKeyPair, error) { - var ( - err error - priv crypto.PrivateKey - derCert []byte - issuerCert *x509.Certificate - issuerKey crypto.PrivateKey - ) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, err - } - - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - CommonName: name, - Organization: []string{"Acme Co"}, - }, - NotBefore: time.Now().Add(-1 *time.Second), - NotAfter: time.Now().AddDate(1, 0, 0), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, - } - if issuer != nil { - issuerCert = issuer.Certificate - issuerKey = *issuer.PrivateKey - } - switch sigAlg { - case x509.SHA1WithRSA: - priv = test1024Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - case x509.SHA256WithRSA: - priv = test2048Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA256WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA256 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.SHA384WithRSA: - priv = test3072Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA384WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA384 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.SHA512WithRSA: - priv = test4096Key - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA512WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA512 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA1: - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - case x509.ECDSAWithSHA256: - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA256WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA256 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA384: - priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA384WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA384 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.ECDSAWithSHA512: - priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA512WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA512 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA256 - } - case x509.DSAWithSHA1: - var dsaPriv dsa.PrivateKey - params := &dsaPriv.Parameters - err = dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160) - if err != nil { - return nil, err - } - err = dsa.GenerateKey(&dsaPriv, rand.Reader) - if err != nil { - return nil, err - } - switch issuerKey.(type) { - case *rsa.PrivateKey: - template.SignatureAlgorithm = x509.SHA1WithRSA - case *ecdsa.PrivateKey: - template.SignatureAlgorithm = x509.ECDSAWithSHA1 - case *dsa.PrivateKey: - template.SignatureAlgorithm = x509.DSAWithSHA1 - } - priv = &dsaPriv - } - if isCA { - template.IsCA = true - template.KeyUsage |= x509.KeyUsageCertSign - template.BasicConstraintsValid = true - } - if issuer == nil { - // no issuer given,make this a self-signed root cert - issuerCert = &template - issuerKey = priv - } - - log.Println("creating cert", name, "issued by", issuerCert.Subject.CommonName, "with sigalg", sigAlg) - switch priv.(type) { - case *rsa.PrivateKey: - switch issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*rsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) - } - case *ecdsa.PrivateKey: - switch issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*rsa.PrivateKey)) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*ecdsa.PrivateKey)) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*ecdsa.PrivateKey).Public(), issuerKey.(*dsa.PrivateKey)) - } - case *dsa.PrivateKey: - pub := &priv.(*dsa.PrivateKey).PublicKey - switch issuerKey := issuerKey.(type) { - case *rsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, pub, issuerKey) - case *ecdsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) - case *dsa.PrivateKey: - derCert, err = x509.CreateCertificate(rand.Reader, &template, issuerCert, priv.(*dsa.PublicKey), issuerKey) - } - } - if err != nil { - return nil, err - } - if len(derCert) == 0 { - return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) - } - cert, err := x509.ParseCertificate(derCert) - if err != nil { - return nil, err - } - pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - return &certKeyPair{ - Certificate: cert, - PrivateKey: &priv, - }, nil -} - -type TestFixture struct { - Input []byte - Certificate *x509.Certificate - PrivateKey *rsa.PrivateKey -} - -func UnmarshalTestFixture(testPEMBlock string) TestFixture { - var result TestFixture - var derBlock *pem.Block - var pemBlock = []byte(testPEMBlock) - for { - derBlock, pemBlock = pem.Decode(pemBlock) - if derBlock == nil { - break - } - switch derBlock.Type { - case "PKCS7": - result.Input = derBlock.Bytes - case "CERTIFICATE": - result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes) - case "PRIVATE KEY": - result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes) - } - } - - return result -} diff --git a/scep/pkcs7/sign.go b/scep/pkcs7/sign.go deleted file mode 100644 index addd7638..00000000 --- a/scep/pkcs7/sign.go +++ /dev/null @@ -1,429 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto" - "crypto/dsa" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "math/big" - "time" -) - -// SignedData is an opaque data structure for creating signed data payloads -type SignedData struct { - sd signedData - certs []*x509.Certificate - data, messageDigest []byte - digestOid asn1.ObjectIdentifier - encryptionOid asn1.ObjectIdentifier -} - -// NewSignedData takes data and initializes a PKCS7 SignedData struct that is -// ready to be signed via AddSigner. The digest algorithm is set to SHA1 by default -// and can be changed by calling SetDigestAlgorithm. -func NewSignedData(data []byte) (*SignedData, error) { - content, err := asn1.Marshal(data) - if err != nil { - return nil, err - } - ci := contentInfo{ - ContentType: OIDData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, - } - sd := signedData{ - ContentInfo: ci, - Version: 1, - } - return &SignedData{sd: sd, data: data, digestOid: OIDDigestAlgorithmSHA1}, nil -} - -// SignerInfoConfig are optional values to include when adding a signer -type SignerInfoConfig struct { - ExtraSignedAttributes []Attribute - ExtraUnsignedAttributes []Attribute -} - -type signedData struct { - Version int `asn1:"default:1"` - DigestAlgorithmIdentifiers []pkix.AlgorithmIdentifier `asn1:"set"` - ContentInfo contentInfo - Certificates rawCertificates `asn1:"optional,tag:0"` - CRLs []pkix.CertificateList `asn1:"optional,tag:1"` - SignerInfos []signerInfo `asn1:"set"` -} - -type signerInfo struct { - Version int `asn1:"default:1"` - IssuerAndSerialNumber issuerAndSerial - DigestAlgorithm pkix.AlgorithmIdentifier - AuthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:0"` - DigestEncryptionAlgorithm pkix.AlgorithmIdentifier - EncryptedDigest []byte - UnauthenticatedAttributes []attribute `asn1:"optional,omitempty,tag:1"` -} - -type attribute struct { - Type asn1.ObjectIdentifier - Value asn1.RawValue `asn1:"set"` -} - -func marshalAttributes(attrs []attribute) ([]byte, error) { - encodedAttributes, err := asn1.Marshal(struct { - A []attribute `asn1:"set"` - }{A: attrs}) - if err != nil { - return nil, err - } - - // Remove the leading sequence octets - var raw asn1.RawValue - asn1.Unmarshal(encodedAttributes, &raw) - return raw.Bytes, nil -} - -type rawCertificates struct { - Raw asn1.RawContent -} - -type issuerAndSerial struct { - IssuerName asn1.RawValue - SerialNumber *big.Int -} - -// SetDigestAlgorithm sets the digest algorithm to be used in the signing process. -// -// This should be called before adding signers -func (sd *SignedData) SetDigestAlgorithm(d asn1.ObjectIdentifier) { - sd.digestOid = d -} - -// SetEncryptionAlgorithm sets the encryption algorithm to be used in the signing process. -// -// This should be called before adding signers -func (sd *SignedData) SetEncryptionAlgorithm(d asn1.ObjectIdentifier) { - sd.encryptionOid = d -} - -// AddSigner is a wrapper around AddSignerChain() that adds a signer without any parent. -func (sd *SignedData) AddSigner(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { - var parents []*x509.Certificate - return sd.AddSignerChain(ee, pkey, parents, config) -} - -// AddSignerChain signs attributes about the content and adds certificates -// and signers infos to the Signed Data. The certificate and private key -// of the end-entity signer are used to issue the signature, and any -// parent of that end-entity that need to be added to the list of -// certifications can be specified in the parents slice. -// -// The signature algorithm used to hash the data is the one of the end-entity -// certificate. -func (sd *SignedData) AddSignerChain(ee *x509.Certificate, pkey crypto.PrivateKey, parents []*x509.Certificate, config SignerInfoConfig) error { -// Following RFC 2315, 9.2 SignerInfo type, the distinguished name of -// the issuer of the end-entity signer is stored in the issuerAndSerialNumber -// section of the SignedData.SignerInfo, alongside the serial number of -// the end-entity. - var ias issuerAndSerial - ias.SerialNumber = ee.SerialNumber - if len(parents) == 0 { - // no parent, the issuer is the end-entity cert itself - ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} - } else { - err := verifyPartialChain(ee, parents) - if err != nil { - return err - } - // the first parent is the issuer - ias.IssuerName = asn1.RawValue{FullBytes: parents[0].RawSubject} - } - sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, - pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - ) - hash, err := getHashForOID(sd.digestOid) - if err != nil { - return err - } - h := hash.New() - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) - encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) - if err != nil { - return err - } - attrs := &attributes{} - attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) - attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) - attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) - for _, attr := range config.ExtraSignedAttributes { - attrs.Add(attr.Type, attr.Value) - } - finalAttrs, err := attrs.ForMarshalling() - if err != nil { - return err - } - unsignedAttrs := &attributes{} - for _, attr := range config.ExtraUnsignedAttributes { - unsignedAttrs.Add(attr.Type, attr.Value) - } - finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() - if err != nil { - return err - } - // create signature of signed attributes - signature, err := signAttributes(finalAttrs, pkey, hash) - if err != nil { - return err - } - signer := signerInfo{ - AuthenticatedAttributes: finalAttrs, - UnauthenticatedAttributes: finalUnsignedAttrs, - DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: encryptionOid}, - IssuerAndSerialNumber: ias, - EncryptedDigest: signature, - Version: 1, - } - sd.certs = append(sd.certs, ee) - if len(parents) > 0 { - sd.certs = append(sd.certs, parents...) - } - sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) - return nil -} - -// SignWithoutAttr issues a signature on the content of the pkcs7 SignedData. -// Unlike AddSigner/AddSignerChain, it calculates the digest on the data alone -// and does not include any signed attributes like timestamp and so on. -// -// This function is needed to sign old Android APKs, something you probably -// shouldn't do unless you're maintaining backward compatibility for old -// applications. -func (sd *SignedData) SignWithoutAttr(ee *x509.Certificate, pkey crypto.PrivateKey, config SignerInfoConfig) error { - var signature []byte - sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}) - hash, err := getHashForOID(sd.digestOid) - if err != nil { - return err - } - h := hash.New() - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) - switch pkey := pkey.(type) { - case *dsa.PrivateKey: - // dsa doesn't implement crypto.Signer so we make a special case - // https://github.com/golang/go/issues/27889 - r, s, err := dsa.Sign(rand.Reader, pkey, sd.messageDigest) - if err != nil { - return err - } - signature, err = asn1.Marshal(dsaSignature{r, s}) - if err != nil { - return err - } - default: - key, ok := pkey.(crypto.Signer) - if !ok { - return errors.New("pkcs7: private key does not implement crypto.Signer") - } - signature, err = key.Sign(rand.Reader, sd.messageDigest, hash) - if err != nil { - return err - } - } - var ias issuerAndSerial - ias.SerialNumber = ee.SerialNumber - // no parent, the issue is the end-entity cert itself - ias.IssuerName = asn1.RawValue{FullBytes: ee.RawIssuer} - if sd.encryptionOid == nil { - // if the encryption algorithm wasn't set by SetEncryptionAlgorithm, - // infer it from the digest algorithm - sd.encryptionOid, err = getOIDForEncryptionAlgorithm(pkey, sd.digestOid) - } - if err != nil { - return err - } - signer := signerInfo{ - DigestAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.digestOid}, - DigestEncryptionAlgorithm: pkix.AlgorithmIdentifier{Algorithm: sd.encryptionOid}, - IssuerAndSerialNumber: ias, - EncryptedDigest: signature, - Version: 1, - } - // create signature of signed attributes - sd.certs = append(sd.certs, ee) - sd.sd.SignerInfos = append(sd.sd.SignerInfos, signer) - return nil -} - -func (si *signerInfo) SetUnauthenticatedAttributes(extraUnsignedAttrs []Attribute) error { - unsignedAttrs := &attributes{} - for _, attr := range extraUnsignedAttrs { - unsignedAttrs.Add(attr.Type, attr.Value) - } - finalUnsignedAttrs, err := unsignedAttrs.ForMarshalling() - if err != nil { - return err - } - - si.UnauthenticatedAttributes = finalUnsignedAttrs - - return nil -} - -// AddCertificate adds the certificate to the payload. Useful for parent certificates -func (sd *SignedData) AddCertificate(cert *x509.Certificate) { - sd.certs = append(sd.certs, cert) -} - -// Detach removes content from the signed data struct to make it a detached signature. -// This must be called right before Finish() -func (sd *SignedData) Detach() { - sd.sd.ContentInfo = contentInfo{ContentType: OIDData} -} - -// GetSignedData returns the private Signed Data -func (sd *SignedData) GetSignedData() *signedData { - return &sd.sd -} - -// Finish marshals the content and its signers -func (sd *SignedData) Finish() ([]byte, error) { - sd.sd.Certificates = marshalCertificates(sd.certs) - inner, err := asn1.Marshal(sd.sd) - if err != nil { - return nil, err - } - outer := contentInfo{ - ContentType: OIDSignedData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: inner, IsCompound: true}, - } - return asn1.Marshal(outer) -} - -// RemoveAuthenticatedAttributes removes authenticated attributes from signedData -// similar to OpenSSL's PKCS7_NOATTR or -noattr flags -func (sd *SignedData) RemoveAuthenticatedAttributes() { - for i := range sd.sd.SignerInfos { - sd.sd.SignerInfos[i].AuthenticatedAttributes = nil - } -} - -// RemoveUnauthenticatedAttributes removes unauthenticated attributes from signedData -func (sd *SignedData) RemoveUnauthenticatedAttributes() { - for i := range sd.sd.SignerInfos { - sd.sd.SignerInfos[i].UnauthenticatedAttributes = nil - } -} - -// verifyPartialChain checks that a given cert is issued by the first parent in the list, -// then continue down the path. It doesn't require the last parent to be a root CA, -// or to be trusted in any truststore. It simply verifies that the chain provided, albeit -// partial, makes sense. -func verifyPartialChain(cert *x509.Certificate, parents []*x509.Certificate) error { - if len(parents) == 0 { - return fmt.Errorf("pkcs7: zero parents provided to verify the signature of certificate %q", cert.Subject.CommonName) - } - err := cert.CheckSignatureFrom(parents[0]) - if err != nil { - return fmt.Errorf("pkcs7: certificate signature from parent is invalid: %v", err) - } - if len(parents) == 1 { - // there is no more parent to check, return - return nil - } - return verifyPartialChain(parents[0], parents[1:]) -} - -func cert2issuerAndSerial(cert *x509.Certificate) (issuerAndSerial, error) { - var ias issuerAndSerial - // The issuer RDNSequence has to match exactly the sequence in the certificate - // We cannot use cert.Issuer.ToRDNSequence() here since it mangles the sequence - ias.IssuerName = asn1.RawValue{FullBytes: cert.RawIssuer} - ias.SerialNumber = cert.SerialNumber - - return ias, nil -} - -// signs the DER encoded form of the attributes with the private key -func signAttributes(attrs []attribute, pkey crypto.PrivateKey, digestAlg crypto.Hash) ([]byte, error) { - attrBytes, err := marshalAttributes(attrs) - if err != nil { - return nil, err - } - h := digestAlg.New() - h.Write(attrBytes) - hash := h.Sum(nil) - - // dsa doesn't implement crypto.Signer so we make a special case - // https://github.com/golang/go/issues/27889 - switch pkey := pkey.(type) { - case *dsa.PrivateKey: - r, s, err := dsa.Sign(rand.Reader, pkey, hash) - if err != nil { - return nil, err - } - return asn1.Marshal(dsaSignature{r, s}) - } - - key, ok := pkey.(crypto.Signer) - if !ok { - return nil, errors.New("pkcs7: private key does not implement crypto.Signer") - } - return key.Sign(rand.Reader, hash, digestAlg) -} - -type dsaSignature struct { - R, S *big.Int -} - -// concats and wraps the certificates in the RawValue structure -func marshalCertificates(certs []*x509.Certificate) rawCertificates { - var buf bytes.Buffer - for _, cert := range certs { - buf.Write(cert.Raw) - } - rawCerts, _ := marshalCertificateBytes(buf.Bytes()) - return rawCerts -} - -// Even though, the tag & length are stripped out during marshalling the -// RawContent, we have to encode it into the RawContent. If its missing, -// then `asn1.Marshal()` will strip out the certificate wrapper instead. -func marshalCertificateBytes(certs []byte) (rawCertificates, error) { - var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} - b, err := asn1.Marshal(val) - if err != nil { - return rawCertificates{}, err - } - return rawCertificates{Raw: b}, nil -} - -// DegenerateCertificate creates a signed data structure containing only the -// provided certificate or certificate chain. -func DegenerateCertificate(cert []byte) ([]byte, error) { - rawCert, err := marshalCertificateBytes(cert) - if err != nil { - return nil, err - } - emptyContent := contentInfo{ContentType: OIDData} - sd := signedData{ - Version: 1, - ContentInfo: emptyContent, - Certificates: rawCert, - CRLs: []pkix.CertificateList{}, - } - content, err := asn1.Marshal(sd) - if err != nil { - return nil, err - } - signedContent := contentInfo{ - ContentType: OIDSignedData, - Content: asn1.RawValue{Class: 2, Tag: 0, Bytes: content, IsCompound: true}, - } - return asn1.Marshal(signedContent) -} diff --git a/scep/pkcs7/sign_test.go b/scep/pkcs7/sign_test.go deleted file mode 100644 index 0ba6324d..00000000 --- a/scep/pkcs7/sign_test.go +++ /dev/null @@ -1,266 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/dsa" - "crypto/x509" - "encoding/asn1" - "encoding/pem" - "fmt" - "io/ioutil" - "log" - "math/big" - "os" - "os/exec" - "testing" -) - -func TestSign(t *testing.T) { - content := []byte("Hello World") - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - x509.ECDSAWithSHA1, - x509.ECDSAWithSHA256, - x509.ECDSAWithSHA384, - x509.ECDSAWithSHA512, - } - for _, sigalgroot := range sigalgs { - rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) - if err != nil { - t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) - } - truststore := x509.NewCertPool() - truststore.AddCert(rootCert.Certificate) - for _, sigalginter := range sigalgs { - interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) - if err != nil { - t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) - } - var parents []*x509.Certificate - parents = append(parents, interCert.Certificate) - for _, sigalgsigner := range sigalgs { - signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - for _, testDetach := range []bool{false, true} { - log.Printf("test %s/%s/%s detached %t\n", sigalgroot, sigalginter, sigalgsigner, testDetach) - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot initialize signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - - // Set the digest to match the end entity cert - signerDigest, _ := getDigestOIDForSignatureAlgorithm(signerCert.Certificate.SignatureAlgorithm) - toBeSigned.SetDigestAlgorithm(signerDigest) - - if err := toBeSigned.AddSignerChain(signerCert.Certificate, *signerCert.PrivateKey, parents, SignerInfoConfig{}); err != nil { - t.Fatalf("test %s/%s/%s: cannot add signer: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if testDetach { - toBeSigned.Detach() - } - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("test %s/%s/%s: cannot finish signing data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: signed}) - p7, err := Parse(signed) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot parse signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if testDetach { - p7.Content = content - } - if !bytes.Equal(content, p7.Content) { - t.Errorf("test %s/%s/%s: content was not found in the parsed data:\n\tExpected: %s\n\tActual: %s", sigalgroot, sigalginter, sigalgsigner, content, p7.Content) - } - if err := p7.VerifyWithChain(truststore); err != nil { - t.Errorf("test %s/%s/%s: cannot verify signed data: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - if !signerDigest.Equal(p7.Signers[0].DigestAlgorithm.Algorithm) { - t.Errorf("test %s/%s/%s: expected digest algorithm %q but got %q", - sigalgroot, sigalginter, sigalgsigner, signerDigest, p7.Signers[0].DigestAlgorithm.Algorithm) - } - } - } - } - } -} - -func TestDSASignAndVerifyWithOpenSSL(t *testing.T) { - content := []byte("Hello World") - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - - block, _ := pem.Decode([]byte(dsaPublicCert)) - if block == nil { - t.Fatal("failed to parse certificate PEM") - } - signerCert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - t.Fatal("failed to parse certificate: " + err.Error()) - } - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signer") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) - - priv := dsa.PrivateKey{ - PublicKey: dsa.PublicKey{Parameters: dsa.Parameters{P: fromHex("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7"), - Q: fromHex("9760508F15230BCCB292B982A2EB840BF0581CF5"), - G: fromHex("F7E1A085D69B3DDECBBCAB5C36B857B97994AFBBFA3AEA82F9574C0B3D0782675159578EBAD4594FE67107108180B449167123E84C281613B7CF09328CC8A6E13C167A8B547C8D28E0A3AE1E2BB3A675916EA37F0BFA213562F1FB627A01243BCCA4F1BEA8519089A883DFE15AE59F06928B665E807B552564014C3BFECF492A"), - }, - }, - X: fromHex("7D6E1A3DD4019FD809669D8AB8DA73807CEF7EC1"), - } - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("test case: cannot initialize signed data: %s", err) - } - if err := toBeSigned.SignWithoutAttr(signerCert, &priv, SignerInfoConfig{}); err != nil { - t.Fatalf("Cannot add signer: %s", err) - } - toBeSigned.Detach() - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("test case: cannot finish signing data: %s", err) - } - - // write the signature to a temp file - tmpSignatureFile, err := ioutil.TempFile("", "TestDSASignAndVerifyWithOpenSSL_signature") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignatureFile.Name(), pem.EncodeToMemory(&pem.Block{Type: "PKCS7", Bytes: signed}), 0755) - - // call openssl to verify the signature on the content using the root - opensslCMD := exec.Command("openssl", "smime", "-verify", "-noverify", - "-in", tmpSignatureFile.Name(), "-inform", "PEM", - "-content", tmpContentFile.Name()) - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("test case: openssl command failed with %s: %s", err, out) - } - os.Remove(tmpSignatureFile.Name()) // clean up - os.Remove(tmpContentFile.Name()) // clean up - os.Remove(tmpSignerCertFile.Name()) // clean up -} - -func ExampleSignedData() { - // generate a signing cert or load a key pair - cert, err := createTestCertificate(x509.SHA256WithRSA) - if err != nil { - fmt.Printf("Cannot create test certificates: %s", err) - } - - // Initialize a SignedData struct with content to be signed - signedData, err := NewSignedData([]byte("Example data to be signed")) - if err != nil { - fmt.Printf("Cannot initialize signed data: %s", err) - } - - // Add the signing cert and private key - if err := signedData.AddSigner(cert.Certificate, cert.PrivateKey, SignerInfoConfig{}); err != nil { - fmt.Printf("Cannot add signer: %s", err) - } - - // Call Detach() is you want to remove content from the signature - // and generate an S/MIME detached signature - signedData.Detach() - - // Finish() to obtain the signature bytes - detachedSignature, err := signedData.Finish() - if err != nil { - fmt.Printf("Cannot finish signing data: %s", err) - } - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: detachedSignature}) -} - -func TestUnmarshalSignedAttribute(t *testing.T) { - cert, err := createTestCertificate(x509.SHA512WithRSA) - if err != nil { - t.Fatal(err) - } - content := []byte("Hello World") - toBeSigned, err := NewSignedData(content) - if err != nil { - t.Fatalf("Cannot initialize signed data: %s", err) - } - oidTest := asn1.ObjectIdentifier{2, 3, 4, 5, 6, 7} - testValue := "TestValue" - if err := toBeSigned.AddSigner(cert.Certificate, *cert.PrivateKey, SignerInfoConfig{ - ExtraSignedAttributes: []Attribute{Attribute{Type: oidTest, Value: testValue}}, - }); err != nil { - t.Fatalf("Cannot add signer: %s", err) - } - signed, err := toBeSigned.Finish() - if err != nil { - t.Fatalf("Cannot finish signing data: %s", err) - } - p7, err := Parse(signed) - if err != nil { - t.Fatalf("Cannot parse signed data: %v", err) - } - var actual string - err = p7.UnmarshalSignedAttribute(oidTest, &actual) - if err != nil { - t.Fatalf("Cannot unmarshal test value: %s", err) - } - if testValue != actual { - t.Errorf("Attribute does not match test value\n\tExpected: %s\n\tActual: %s", testValue, actual) - } -} - -func TestDegenerateCertificate(t *testing.T) { - cert, err := createTestCertificate(x509.SHA1WithRSA) - if err != nil { - t.Fatal(err) - } - deg, err := DegenerateCertificate(cert.Certificate.Raw) - if err != nil { - t.Fatal(err) - } - testOpenSSLParse(t, deg) - pem.Encode(os.Stdout, &pem.Block{Type: "PKCS7", Bytes: deg}) -} - -// writes the cert to a temporary file and tests that openssl can read it. -func testOpenSSLParse(t *testing.T, certBytes []byte) { - tmpCertFile, err := ioutil.TempFile("", "testCertificate") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpCertFile.Name()) // clean up - - if _, err := tmpCertFile.Write(certBytes); err != nil { - t.Fatal(err) - } - - opensslCMD := exec.Command("openssl", "pkcs7", "-inform", "der", "-in", tmpCertFile.Name()) - _, err = opensslCMD.Output() - if err != nil { - t.Fatal(err) - } - - if err := tmpCertFile.Close(); err != nil { - t.Fatal(err) - } - -} -func fromHex(s string) *big.Int { - result, ok := new(big.Int).SetString(s, 16) - if !ok { - panic(s) - } - return result -} diff --git a/scep/pkcs7/verify.go b/scep/pkcs7/verify.go deleted file mode 100644 index c8ead236..00000000 --- a/scep/pkcs7/verify.go +++ /dev/null @@ -1,264 +0,0 @@ -package pkcs7 - -import ( - "crypto/subtle" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "time" -) - -// Verify is a wrapper around VerifyWithChain() that initializes an empty -// trust store, effectively disabling certificate verification when validating -// a signature. -func (p7 *PKCS7) Verify() (err error) { - return p7.VerifyWithChain(nil) -} - -// VerifyWithChain checks the signatures of a PKCS7 object. -// If truststore is not nil, it also verifies the chain of trust of the end-entity -// signer cert to one of the root in the truststore. -func (p7 *PKCS7) VerifyWithChain(truststore *x509.CertPool) (err error) { - if len(p7.Signers) == 0 { - return errors.New("pkcs7: Message has no signers") - } - for _, signer := range p7.Signers { - if err := verifySignature(p7, signer, truststore); err != nil { - return err - } - } - return nil -} - -func verifySignature(p7 *PKCS7, signer signerInfo, truststore *x509.CertPool) (err error) { - signedData := p7.Content - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) - if ee == nil { - return errors.New("pkcs7: No certificate for signer") - } - signingTime := time.Now().UTC() - if len(signer.AuthenticatedAttributes) > 0 { - // TODO(fullsailor): First check the content type match - var digest []byte - err := unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeMessageDigest, &digest) - if err != nil { - return err - } - hash, err := getHashForOID(signer.DigestAlgorithm.Algorithm) - if err != nil { - return err - } - h := hash.New() - h.Write(p7.Content) - computed := h.Sum(nil) - if subtle.ConstantTimeCompare(digest, computed) != 1 { - return &MessageDigestMismatchError{ - ExpectedDigest: digest, - ActualDigest: computed, - } - } - signedData, err = marshalAttributes(signer.AuthenticatedAttributes) - if err != nil { - return err - } - err = unmarshalAttribute(signer.AuthenticatedAttributes, OIDAttributeSigningTime, &signingTime) - if err == nil { - // signing time found, performing validity check - if signingTime.After(ee.NotAfter) || signingTime.Before(ee.NotBefore) { - return fmt.Errorf("pkcs7: signing time %q is outside of certificate validity %q to %q", - signingTime.Format(time.RFC3339), - ee.NotBefore.Format(time.RFC3339), - ee.NotBefore.Format(time.RFC3339)) - } - } - } - if truststore != nil { - _, err = verifyCertChain(ee, p7.Certificates, truststore, signingTime) - if err != nil { - return err - } - } - sigalg, err := getSignatureAlgorithm(signer.DigestEncryptionAlgorithm, signer.DigestAlgorithm) - if err != nil { - return err - } - return ee.CheckSignature(sigalg, signedData, signer.EncryptedDigest) -} - -// GetOnlySigner returns an x509.Certificate for the first signer of the signed -// data payload. If there are more or less than one signer, nil is returned -func (p7 *PKCS7) GetOnlySigner() *x509.Certificate { - if len(p7.Signers) != 1 { - return nil - } - signer := p7.Signers[0] - return getCertFromCertsByIssuerAndSerial(p7.Certificates, signer.IssuerAndSerialNumber) -} - -// UnmarshalSignedAttribute decodes a single attribute from the signer info -func (p7 *PKCS7) UnmarshalSignedAttribute(attributeType asn1.ObjectIdentifier, out interface{}) error { - sd, ok := p7.raw.(signedData) - if !ok { - return errors.New("pkcs7: payload is not signedData content") - } - if len(sd.SignerInfos) < 1 { - return errors.New("pkcs7: payload has no signers") - } - attributes := sd.SignerInfos[0].AuthenticatedAttributes - return unmarshalAttribute(attributes, attributeType, out) -} - -func parseSignedData(data []byte) (*PKCS7, error) { - var sd signedData - asn1.Unmarshal(data, &sd) - certs, err := sd.Certificates.Parse() - if err != nil { - return nil, err - } - // fmt.Printf("--> Signed Data Version %d\n", sd.Version) - - var compound asn1.RawValue - var content unsignedData - - // The Content.Bytes maybe empty on PKI responses. - if len(sd.ContentInfo.Content.Bytes) > 0 { - if _, err := asn1.Unmarshal(sd.ContentInfo.Content.Bytes, &compound); err != nil { - return nil, err - } - } - // Compound octet string - if compound.IsCompound { - if compound.Tag == 4 { - if _, err = asn1.Unmarshal(compound.Bytes, &content); err != nil { - return nil, err - } - } else { - content = compound.Bytes - } - } else { - // assuming this is tag 04 - content = compound.Bytes - } - return &PKCS7{ - Content: content, - Certificates: certs, - CRLs: sd.CRLs, - Signers: sd.SignerInfos, - raw: sd}, nil -} - -// verifyCertChain takes an end-entity certs, a list of potential intermediates and a -// truststore, and built all potential chains between the EE and a trusted root. -// -// When verifying chains that may have expired, currentTime can be set to a past date -// to allow the verification to pass. If unset, currentTime is set to the current UTC time. -func verifyCertChain(ee *x509.Certificate, certs []*x509.Certificate, truststore *x509.CertPool, currentTime time.Time) (chains [][]*x509.Certificate, err error) { - intermediates := x509.NewCertPool() - for _, intermediate := range certs { - intermediates.AddCert(intermediate) - } - verifyOptions := x509.VerifyOptions{ - Roots: truststore, - Intermediates: intermediates, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - CurrentTime: currentTime, - } - chains, err = ee.Verify(verifyOptions) - if err != nil { - return chains, fmt.Errorf("pkcs7: failed to verify certificate chain: %v", err) - } - return -} - -// MessageDigestMismatchError is returned when the signer data digest does not -// match the computed digest for the contained content -type MessageDigestMismatchError struct { - ExpectedDigest []byte - ActualDigest []byte -} - -func (err *MessageDigestMismatchError) Error() string { - return fmt.Sprintf("pkcs7: Message digest mismatch\n\tExpected: %X\n\tActual : %X", err.ExpectedDigest, err.ActualDigest) -} - -func getSignatureAlgorithm(digestEncryption, digest pkix.AlgorithmIdentifier) (x509.SignatureAlgorithm, error) { - switch { - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA1): - return x509.ECDSAWithSHA1, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA256): - return x509.ECDSAWithSHA256, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA384): - return x509.ECDSAWithSHA384, nil - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmECDSASHA512): - return x509.ECDSAWithSHA512, nil - case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSA), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA1), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA256), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA384), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmRSASHA512): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.SHA1WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.SHA256WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): - return x509.SHA384WithRSA, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): - return x509.SHA512WithRSA, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - case digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSA), - digestEncryption.Algorithm.Equal(OIDDigestAlgorithmDSASHA1): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.DSAWithSHA1, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.DSAWithSHA256, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - case digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP256), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP384), - digestEncryption.Algorithm.Equal(OIDEncryptionAlgorithmECDSAP521): - switch { - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA1): - return x509.ECDSAWithSHA1, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA256): - return x509.ECDSAWithSHA256, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA384): - return x509.ECDSAWithSHA384, nil - case digest.Algorithm.Equal(OIDDigestAlgorithmSHA512): - return x509.ECDSAWithSHA512, nil - default: - return -1, fmt.Errorf("pkcs7: unsupported digest %q for encryption algorithm %q", - digest.Algorithm.String(), digestEncryption.Algorithm.String()) - } - default: - return -1, fmt.Errorf("pkcs7: unsupported algorithm %q", - digestEncryption.Algorithm.String()) - } -} - -func getCertFromCertsByIssuerAndSerial(certs []*x509.Certificate, ias issuerAndSerial) *x509.Certificate { - for _, cert := range certs { - if isCertMatchForIssuerAndSerial(cert, ias) { - return cert - } - } - return nil -} - -func unmarshalAttribute(attrs []attribute, attributeType asn1.ObjectIdentifier, out interface{}) error { - for _, attr := range attrs { - if attr.Type.Equal(attributeType) { - _, err := asn1.Unmarshal(attr.Value.Bytes, out) - return err - } - } - return errors.New("pkcs7: attribute type not in attributes") -} diff --git a/scep/pkcs7/verify_test.go b/scep/pkcs7/verify_test.go deleted file mode 100644 index f80943b2..00000000 --- a/scep/pkcs7/verify_test.go +++ /dev/null @@ -1,713 +0,0 @@ -package pkcs7 - -import ( - "bytes" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - "io/ioutil" - "os" - "os/exec" - "testing" - "time" -) - -func TestVerify(t *testing.T) { - fixture := UnmarshalTestFixture(SignedTestFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } - expected := []byte("We the People") - if !bytes.Equal(p7.Content, expected) { - t.Errorf("Signed content does not match.\n\tExpected:%s\n\tActual:%s", expected, p7.Content) - - } -} - -var SignedTestFixture = ` ------BEGIN PKCS7----- -MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B -BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG -SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh -cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB -Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw -gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP -J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF -a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw -DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8 -zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+ -L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy -axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD -bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI -hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4 -LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG -SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX -iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM -FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIB1TCCAUCgAwIBAgIEabg3LTALBgkqhkiG9w0BAQswKTEQMA4GA1UEChMHQWNt -ZSBDbzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrMB4XDTE1MDUwNjA0MjQ0OFoXDTE2 -MDUwNjA0MjQ0OFowJTEQMA4GA1UEChMHQWNtZSBDbzERMA8GA1UEAxMISm9uIFNu -b3cwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKq/rUxeJmT+azMJV6dcvnK0 -bRabi1xdc7wWYmDHyLwB8KNbWvBNSHMhDydYyIijHMG83qOpAJmcyCUQk3s6vY8F -1LCm0E73Hzh/R5o1JlwUVhV2WKELPzmmhWuOXXh5sBHjEJLklhLWIlSimV7STrX5 -SiE9zK8iRA4oiLTJHcm1AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIAoDALBgkqhkiG -9w0BAQsDgYEAytRMHmVkS/n+2fidJFc2PM16bXAWleC3+cBmSSyVVIKd926SzaU9 -8mnUvvl0CfnL7Vt8AA0MwUkzHbsjhfGX/i+04460Nvhdh2zWylIQUCrncU37/XSR -3Smw/p4AgnUCWJCMtmHsbKDRtAvJ0JIIMmsWXfyevcQ1ceqbot0o/LA= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIICXgIBAAKBgQCqv61MXiZk/mszCVenXL5ytG0Wm4tcXXO8FmJgx8i8AfCjW1rw -TUhzIQ8nWMiIoxzBvN6jqQCZnMglEJN7Or2PBdSwptBO9x84f0eaNSZcFFYVdlih -Cz85poVrjl14ebAR4xCS5JYS1iJUople0k61+UohPcyvIkQOKIi0yR3JtQIDAQAB -AoGBAIPLCR9N+IKxodq11lNXEaUFwMHXc1zqwP8no+2hpz3+nVfplqqubEJ4/PJY -5AgbJoIfnxVhyBXJXu7E+aD/OPneKZrgp58YvHKgGvvPyJg2gpC/1Fh0vQB0HNpI -1ZzIZUl8ZTUtVgtnCBUOh5JGI4bFokAqrT//Uvcfd+idgxqBAkEA1ZbP/Kseld14 -qbWmgmU5GCVxsZRxgR1j4lG3UVjH36KXMtRTm1atAam1uw3OEGa6Y3ANjpU52FaB -Hep5rkk4FQJBAMynMo1L1uiN5GP+KYLEF5kKRxK+FLjXR0ywnMh+gpGcZDcOae+J -+t1gLoWBIESH/Xt639T7smuSfrZSA9V0EyECQA8cvZiWDvLxmaEAXkipmtGPjKzQ -4PsOtkuEFqFl07aKDYKmLUg3aMROWrJidqsIabWxbvQgsNgSvs38EiH3wkUCQQCg -ndxb7piVXb9RBwm3OoU2tE1BlXMX+sVXmAkEhd2dwDsaxrI3sHf1xGXem5AimQRF -JBOFyaCnMotGNioSHY5hAkEAxyXcNixQ2RpLXJTQZtwnbk0XDcbgB+fBgXnv/4f3 -BCvcu85DqJeJyQv44Oe1qsXEX9BfcQIOVaoep35RPlKi9g== ------END PRIVATE KEY-----` - -func TestVerifyEC2(t *testing.T) { - fixture := UnmarshalTestFixture(EC2IdentityDocumentFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Certificates = []*x509.Certificate{fixture.Certificate} - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var EC2IdentityDocumentFixture = ` ------BEGIN PKCS7----- -MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAaCA -JIAEggGmewogICJwcml2YXRlSXAiIDogIjE3Mi4zMC4wLjI1MiIsCiAgImRldnBh -eVByb2R1Y3RDb2RlcyIgOiBudWxsLAogICJhdmFpbGFiaWxpdHlab25lIiA6ICJ1 -cy1lYXN0LTFhIiwKICAidmVyc2lvbiIgOiAiMjAxMC0wOC0zMSIsCiAgImluc3Rh -bmNlSWQiIDogImktZjc5ZmU1NmMiLAogICJiaWxsaW5nUHJvZHVjdHMiIDogbnVs -bCwKICAiaW5zdGFuY2VUeXBlIiA6ICJ0Mi5taWNybyIsCiAgImFjY291bnRJZCIg -OiAiMTIxNjU5MDE0MzM0IiwKICAiaW1hZ2VJZCIgOiAiYW1pLWZjZTNjNjk2IiwK -ICAicGVuZGluZ1RpbWUiIDogIjIwMTYtMDQtMDhUMDM6MDE6MzhaIiwKICAiYXJj -aGl0ZWN0dXJlIiA6ICJ4ODZfNjQiLAogICJrZXJuZWxJZCIgOiBudWxsLAogICJy -YW1kaXNrSWQiIDogbnVsbCwKICAicmVnaW9uIiA6ICJ1cy1lYXN0LTEiCn0AAAAA -AAAxggEYMIIBFAIBATBpMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5n -dG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2Vi -IFNlcnZpY2VzIExMQwIJAJa6SNnlXhpnMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0B -CQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MDgwMzAxNDRaMCMG -CSqGSIb3DQEJBDEWBBTuUc28eBXmImAautC+wOjqcFCBVjAJBgcqhkjOOAQDBC8w -LQIVAKA54NxGHWWCz5InboDmY/GHs33nAhQ6O/ZI86NwjA9Vz3RNMUJrUPU5tAAA -AAAAAA== ------END PKCS7----- ------BEGIN CERTIFICATE----- -MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw -FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD -VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z -ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u -IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl -cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e -ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3 -VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P -hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j -k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U -hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF -lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf -MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW -MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw -vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw -7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K ------END CERTIFICATE-----` - -func TestVerifyAppStore(t *testing.T) { - fixture := UnmarshalTestFixture(AppStoreReceiptFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var AppStoreReceiptFixture = ` ------BEGIN PKCS7----- -MIITtgYJKoZIhvcNAQcCoIITpzCCE6MCAQExCzAJBgUrDgMCGgUAMIIDVwYJKoZI -hvcNAQcBoIIDSASCA0QxggNAMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC -AQEEAwIBADALAgEDAgEBBAMMATEwCwIBCwIBAQQDAgEAMAsCAQ8CAQEEAwIBADAL -AgEQAgEBBAMCAQAwCwIBGQIBAQQDAgEDMAwCAQoCAQEEBBYCNCswDAIBDgIBAQQE -AgIAjTANAgENAgEBBAUCAwFgvTANAgETAgEBBAUMAzEuMDAOAgEJAgEBBAYCBFAy -NDcwGAIBAgIBAQQQDA5jb20uemhpaHUudGVzdDAYAgEEAgECBBCS+ZODNMHwT1Nz -gWYDXyWZMBsCAQACAQEEEwwRUHJvZHVjdGlvblNhbmRib3gwHAIBBQIBAQQU4nRh -YCEZx70Flzv7hvJRjJZckYIwHgIBDAIBAQQWFhQyMDE2LTA3LTIzVDA2OjIxOjEx -WjAeAgESAgEBBBYWFDIwMTMtMDgtMDFUMDc6MDA6MDBaMD0CAQYCAQEENbR21I+a -8+byMXo3NPRoDWQmSXQF2EcCeBoD4GaL//ZCRETp9rGFPSg1KekCP7Kr9HAqw09m -MEICAQcCAQEEOlVJozYYBdugybShbiiMsejDMNeCbZq6CrzGBwW6GBy+DGWxJI91 -Y3ouXN4TZUhuVvLvN1b0m5T3ggQwggFaAgERAgEBBIIBUDGCAUwwCwICBqwCAQEE -AhYAMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgaz -AgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAM -AgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQEwDAICBq4CAQEEAwIBADAMAgIGrwIB -AQQDAgEAMAwCAgaxAgEBBAMCAQAwGwICBqcCAQEEEgwQMTAwMDAwMDIyNTMyNTkw -MTAbAgIGqQIBAQQSDBAxMDAwMDAwMjI1MzI1OTAxMB8CAgaoAgEBBBYWFDIwMTYt -MDctMjNUMDY6MjE6MTFaMB8CAgaqAgEBBBYWFDIwMTYtMDctMjNUMDY6MjE6MTFa -MCACAgamAgEBBBcMFWNvbS56aGlodS50ZXN0LnRlc3RfMaCCDmUwggV8MIIEZKAD -AgECAggO61eH554JjTANBgkqhkiG9w0BAQUFADCBljELMAkGA1UEBhMCVVMxEzAR -BgNVBAoMCkFwcGxlIEluYy4xLDAqBgNVBAsMI0FwcGxlIFdvcmxkd2lkZSBEZXZl -bG9wZXIgUmVsYXRpb25zMUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv -cGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNTExMTMw -MjE1MDlaFw0yMzAyMDcyMTQ4NDdaMIGJMTcwNQYDVQQDDC5NYWMgQXBwIFN0b3Jl -IGFuZCBpVHVuZXMgU3RvcmUgUmVjZWlwdCBTaWduaW5nMSwwKgYDVQQLDCNBcHBs -ZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczETMBEGA1UECgwKQXBwbGUg -SW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQClz4H9JaKBW9aH7SPaMxyO4iPApcQmyz3Gn+xKDVWG/6QC15fKOVRtfX+yVBid -xCxScY5ke4LOibpJ1gjltIhxzz9bRi7GxB24A6lYogQ+IXjV27fQjhKNg0xbKmg3 -k8LyvR7E0qEMSlhSqxLj7d0fmBWQNS3CzBLKjUiB91h4VGvojDE2H0oGDEdU8zeQ -uLKSiX1fpIVK4cCc4Lqku4KXY/Qrk8H9Pm/KwfU8qY9SGsAlCnYO3v6Z/v/Ca/Vb -XqxzUUkIVonMQ5DMjoEC0KCXtlyxoWlph5AQaCYmObgdEHOwCl3Fc9DfdjvYLdmI -HuPsB8/ijtDT+iZVge/iA0kjAgMBAAGjggHXMIIB0zA/BggrBgEFBQcBAQQzMDEw -LwYIKwYBBQUHMAGGI2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcjA0 -MB0GA1UdDgQWBBSRpJz8xHa3n6CK9E31jzZd7SsEhTAMBgNVHRMBAf8EAjAAMB8G -A1UdIwQYMBaAFIgnFwmpthhgi+zruvZHWcVSVKO3MIIBHgYDVR0gBIIBFTCCAREw -ggENBgoqhkiG92NkBQYBMIH+MIHDBggrBgEFBQcCAjCBtgyBs1JlbGlhbmNlIG9u -IHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j -ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25k -aXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0 -aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMDYGCCsGAQUFBwIBFipodHRwOi8vd3d3 -LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eS8wDgYDVR0PAQH/BAQDAgeA -MBAGCiqGSIb3Y2QGCwEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQANphvTLj3jWysH -bkKWbNPojEMwgl/gXNGNvr0PvRr8JZLbjIXDgFnf4+LXLgUUrA3btrj+/DUufMut -F2uOfx/kd7mxZ5W0E16mGYZ2+FogledjjA9z/Ojtxh+umfhlSFyg4Cg6wBA3Lbmg -BDkfc7nIBf3y3n8aKipuKwH8oCBc2et9J6Yz+PWY4L5E27FMZ/xuCk/J4gao0pfz -p45rUaJahHVl0RYEYuPBX/UIqc9o2ZIAycGMs/iNAGS6WGDAfK+PdcppuVsq1h1o -bphC9UynNxmbzDscehlD86Ntv0hgBgw2kivs3hi1EdotI9CO/KBpnBcbnoB7OUdF -MGEvxxOoMIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjEL -MAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxl -IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENB -MB4XDTEzMDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVT -MRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUg -RGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERl -dmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0 -U3rOfGOAYXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkV -CBmsqtsqMu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8 -V25nNYB2NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHl -d0WNUEi6Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1q -arunFjVg0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGj -gaYwgaMwHQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQF -MAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcw -JTAjoCGgH4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/ -BAQDAgGGMBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Z -viz1smwvj+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/N -w0Uwj6ODDc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJ -TleMa1s8Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1V -AKmuu0swruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur -+cmV6U/kTecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxR -pVzscYqCtGwPDBUfMIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQsw -CQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0Ew -HhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzET -MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne -+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjcz -y8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQ -Z48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS -C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINB -hzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIB -djAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9Bp -R5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/ -CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcC -ARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCB -thqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFz -c3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk -IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5 -IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3 -DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizU -sZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJ -fBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr -1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltk -wGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIq -xw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUhMYIByzCCAccCAQEwgaMwgZYxCzAJ -BgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBX -b3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29y -bGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkCCA7rV4fnngmNMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEggEAasPtnide -NWyfUtewW9OSgcQA8pW+5tWMR0469cBPZR84uJa0gyfmPspySvbNOAwnrwzZHYLa -ujOxZLip4DUw4F5s3QwUa3y4BXpF4J+NSn9XNvxNtnT/GcEQtCuFwgJ0o3F0ilhv -MTHrwiwyx/vr+uNDqlORK8lfK+1qNp+A/kzh8eszMrn4JSeTh9ZYxLHE56WkTQGD -VZXl0gKgxSOmDrcp1eQxdlymzrPv9U60wUJ0bkPfrU9qZj3mJrmrkQk61JTe3j6/ -QfjfFBG9JG2mUmYQP1KQ3SypGHzDW8vngvsGu//tNU0NFfOqQu4bYU4VpQl0nPtD -4B85NkrgvQsWAQ== ------END PKCS7-----` - -func TestVerifyApkEcdsa(t *testing.T) { - fixture := UnmarshalTestFixture(ApkEcdsaFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Content, err = base64.StdEncoding.DecodeString(ApkEcdsaContent) - if err != nil { - t.Errorf("Failed to decode base64 signature file: %v", err) - } - if err := p7.Verify(); err != nil { - t.Errorf("Verify failed with error: %v", err) - } -} - -var ApkEcdsaFixture = `-----BEGIN PKCS7----- -MIIDAgYJKoZIhvcNAQcCoIIC8zCCAu8CAQExDzANBglghkgBZQMEAgMFADALBgkq -hkiG9w0BBwGgggH3MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAME -MBIxEDAOBgNVBAMMB2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUz -MTIyWjASMRAwDgYDVQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG -AAQAYX95sSjPEQqgyLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9G -hAyIyQSnNrIg0zDlWQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCP -B0p+fEXDbq8QFDYvlh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1Ud -DgQWBBT/Ra3kz60gQ7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tY -k3byZckcLabt8TAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39 -hYLsWk2H84oEw+HJqGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcD -j2VfNCHazuXO3kSxGA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJV -A/2uEoeqvnl7BsdkicyaOBNEADijuVdaPPIWzKClt9OaVxExgdAwgc0CAQEwHzAS -MRAwDgYDVQQDDAdlYy1wNTIxAgkA7Fd0Wy+bdiIwDQYJYIZIAWUDBAIDBQAwCgYI -KoZIzj0EAwQEgYswgYgCQgD1pVSNo7qTm9A6tpt3SU2yRa+xpJAnUbpZ+Gu36B71 -JnQBUzRgTGevniqHpyagi7b2zjWh1uvfz9FfrITUwGMddgJCAPjiBRcl7rKpxmZn -V1MvcJOX41xRSJu1wmBiYXqaJarL+gQ/Wl7RYsMtqLjmNColvLaHNxCaWOO/8nAE -Hg0OMA60 ------END PKCS7-----` - -var ApkEcdsaContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KU0hBLTUxMi1EaWdlc3QtTWFuaWZlc3Q6IFAvVDRqSWtTMjQvNzFxeFE2WW1MeEtNdkRPUUF0WjUxR090dFRzUU9yemhHRQ0KIEpaUGVpWUtyUzZYY090bStYaWlFVC9uS2tYdWVtUVBwZ2RBRzFKUzFnPT0NCkNyZWF0ZWQtQnk6IDEuMCAoQW5kcm9pZCBTaWduQXBrKQ0KDQpOYW1lOiBBbmRyb2lkTWFuaWZlc3QueG1sDQpTSEEtNTEyLURpZ2VzdDogcm9NbWVQZmllYUNQSjFJK2VzMVpsYis0anB2UXowNHZqRWVpL2U0dkN1ald0VVVWSHEzMkNXDQogMUxsOHZiZGMzMCtRc1FlN29ibld4dmhLdXN2K3c1a2c9PQ0KDQpOYW1lOiByZXNvdXJjZXMuYXJzYw0KU0hBLTUxMi1EaWdlc3Q6IG5aYW1aUzlPZTRBRW41cEZaaCtoQ1JFT3krb1N6a3hHdU5YZU0wUFF6WGVBVlVQV3hSVzFPYQ0KIGVLbThRbXdmTmhhaS9HOEcwRUhIbHZEQWdlcy9HUGtBPT0NCg0KTmFtZTogY2xhc3Nlcy5kZXgNClNIQS01MTItRGlnZXN0OiBlbWlDQld2bkVSb0g2N2lCa3EwcUgrdm5tMkpaZDlMWUNEV051N3RNYzJ3bTRtV0dYSUVpWmcNCiBWZkVPV083MFRlZnFjUVhldkNtN2hQMnRpT0U3Y0w5UT09DQoNCg==` - -func TestVerifyFirefoxAddon(t *testing.T) { - fixture := UnmarshalTestFixture(FirefoxAddonFixture) - p7, err := Parse(fixture.Input) - if err != nil { - t.Errorf("Parse encountered unexpected error: %v", err) - } - p7.Content = FirefoxAddonContent - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(FirefoxAddonRootCert) - if err := p7.VerifyWithChain(certPool); err != nil { - t.Errorf("Verify failed with error: %v", err) - } - // Verify the certificate chain to make sure the identified root - // is the one we expect - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) - if ee == nil { - t.Errorf("No end-entity certificate found for signer") - } - signingTime, _ := time.Parse(time.RFC3339, "2017-02-23 09:06:16-05:00") - chains, err := verifyCertChain(ee, p7.Certificates, certPool, signingTime) - if err != nil { - t.Error(err) - } - if len(chains) != 1 { - t.Errorf("Expected to find one chain, but found %d", len(chains)) - } - if len(chains[0]) != 3 { - t.Errorf("Expected to find three certificates in chain, but found %d", len(chains[0])) - } - if chains[0][0].Subject.CommonName != "tabscope@xuldev.org" { - t.Errorf("Expected to find EE certificate with subject 'tabscope@xuldev.org', but found '%s'", chains[0][0].Subject.CommonName) - } - if chains[0][1].Subject.CommonName != "production-signing-ca.addons.mozilla.org" { - t.Errorf("Expected to find intermediate certificate with subject 'production-signing-ca.addons.mozilla.org', but found '%s'", chains[0][1].Subject.CommonName) - } - if chains[0][2].Subject.CommonName != "root-ca-production-amo" { - t.Errorf("Expected to find root certificate with subject 'root-ca-production-amo', but found '%s'", chains[0][2].Subject.CommonName) - } -} - -var FirefoxAddonContent = []byte(`Signature-Version: 1.0 -MD5-Digest-Manifest: KjRavc6/KNpuT1QLcB/Gsg== -SHA1-Digest-Manifest: 5Md5nUg+U7hQ/UfzV+xGKWOruVI= - -`) - -var FirefoxAddonFixture = ` ------BEGIN PKCS7----- -MIIQTAYJKoZIhvcNAQcCoIIQPTCCEDkCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3 -DQEHAaCCDL0wggW6MIIDoqADAgECAgYBVpobWVwwDQYJKoZIhvcNAQELBQAwgcUx -CzAJBgNVBAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYD -VQQLEyZNb3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8G -A1UEAxMocHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0 -MDIGCSqGSIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxh -LmNvbTAeFw0xNjA4MTcyMDA0NThaFw0yMTA4MTYyMDA0NThaMHYxEzARBgNVBAsT -ClByb2R1Y3Rpb24xCzAJBgNVBAYTAlVTMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3 -MQ8wDQYDVQQKEwZBZGRvbnMxCzAJBgNVBAgTAkNBMRwwGgYDVQQDFBN0YWJzY29w -ZUB4dWxkZXYub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv6e0 -mPD8dt4J8HTNNq4ODns2DV6Weh1hllCIFvOeu1u3UrR03st0BMY8OXYwr/NvRVjg -bA8gRySWAL+XqLzbhtXNeNegAoxrF+3mYY5rJjsLj/FGI6P6OXjngqwgm9VTBl7m -jh/KXBSwYoUcavJo6cmk8sCFwoblyQiv+tsWaUCOI6zMzubNtIS+GFvET9y/VZMP -j6mk8O10wBgJF5MMtA19va3qXy7aCZ7DnZp1l3equd/L6t324TtXoqx6xWQKo6TM -I0mcTlKvm6TKegTGBCyGn3JRARoIJv4AW1qqgyaHXf9EoY2pKT8Avkri5++NuSJ6 -jtO4k/diBA2MZU20U0KGffYZNTxKDqd6XtI6y1tJPd/OWRFyU+mHntkcm9sar7L3 -nPKujHRox2re10ec1WBnJE3PjlAoesNjxzp+xs2mGGc8DX9NuWn+1uK9xmgGIIMl -OFfyQ4s0G6hKp5goFcrFZxmexu0ZahOs8vZf8xDBW7yR1zToQElOXHvrscM386os -kOF9IxQZfcCoPuNQVg1haCONNkx0oau3RQQlOSAZtC79b+rBjQ5JYfjRLYAworf2 -xQaprCh33TD1dTBrvzEbCGszgkN53Vqh5TFBjbU/NyldOkGvK8Xf6WhT5u+aftnV -lbuE2McAg6x1AlloUZq6PNTBpz7zypcIISnQ+y8CAwEAATANBgkqhkiG9w0BAQsF -AAOCAgEAIBoo2+OEYNCgP/IbUj9azaf/lde1q4AK/uTMoUeS5WcrXd8aqA0Y1qV7 -xUALgDQAExXgqcOMGu4mPMaoZDgwGI4Tj7XPJQq5Z5zYxpRf/Wtzae33T9BF6QPW -v5xiRYuol+FbEtqRHZqxDWtIrd1MWBy3wjO3pLPdzDM9jWh+HLxdGWThJszaZp3T -CqsOx+l9W0Q7qM5ioZpHStgXDfhw38Lg++kLnzcX9MqsjYyezdwE4krqW6hK3+4S -0LZE4dTgsy8JULkyAF3HrPWEXESnD7c4mx6owZe+BNDK5hsVM/obAqH7sJq/igbM -5N1l832p/ws8l5xKOr3qBWSzWn6u7ExvqG6Ckh0foJOVXvzGqvrXcoiBGV8S9Z7c -DghUvMt6b0pZ0ildRCHfTUz7eG3g4MhfbjupR7b+L9FWEJhcd/H0dxpw7SKYha/n -ePuRL7MXmbW8WLMqO/ImxzL8TPOB3pUg3nITfubV6gpPBmn+0nwbqYUmggJuwgvK -I2GpN2Ny6EErZy17EEgyhJygJZMj+UzQjC781xxsl3ljpYEqqwgRLIZBSBUD5dXj -XBuU24w162SeSyHZzkBbuv6lr52pqoZyFrG29DCHECgO9ZmNWgSpiWSkh+vExAG7 -wNs0y61t2HUG+BCMGPQ9sOzouyTfrnLVAWwzswGftFYQfoIBeJIwggb7MIIE46AD -AgECAgMQAAIwDQYJKoZIhvcNAQEMBQAwfTELMAkGA1UEBhMCVVMxHDAaBgNVBAoT -E01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1 -Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNhLXByb2R1Y3Rp -b24tYW1vMB4XDTE1MDMxNzIzNTI0MloXDTI1MDMxNDIzNTI0MlowgcUxCzAJBgNV -BAYTAlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZN -b3ppbGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTExMC8GA1UEAxMo -cHJvZHVjdGlvbi1zaWduaW5nLWNhLmFkZG9ucy5tb3ppbGxhLm9yZzE0MDIGCSqG -SIb3DQEJARYlc2VydmljZXMtb3BzK2FkZG9uc2lnbmluZ0Btb3ppbGxhLmNvbTCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMLMM9m2HBLhCiO9mhljpehT -hxpzlCnxluzDZ51I/H7MvBbIvZBm9zSpHdffubSsak2qYE69d+ebTa/CK83WIosM -24/2Qp7n/GGaPJcCC4Y3JkrCsgA8+wV2MbFlKSv+qMdvI/sE3BPYDMCjVPMhHmIP -XaPWd42OoHpI8R3GGUtVnR3Hm76pa2+v6TwgeMiO8om+ogGufiyv6FNMZ5NuY1Z9 -aLNEvehnAzSfddQyki+6FJd7XkgZbP7pb1Kl8yYgiy4piBerJ9H09uPehffE3Ell -3cApQL3+0kjaUX4scMjuNQDMKziRZkYgJAM+qA9WA5Jn77AjerQBWQeEev1PWHYh -0IDlgS/a0bjKmVjNZYG6adrY/R5/whzWGFCIE1UfhPm6PdN0557qvF838C2RFHsI -KzV6KQf0chMjpa02tPaIctjVhnDQZZNKm2ZfLOt9kQ57Is/e6KxH7pYMit46+s99 -lYM7ZquvWbK19b1Ili/6S1BxSzd3wztgfN5jGsc+jCCYLm+AcVtfNKc8cFZHXKrB -CwhGmdbWDSBCicZNA7FKJpO3oIx26VPF2XUldA/T5Mh/POGLilK3t9m9qbjEyDp1 -EwoBToOR/aMrdnNYvSWp0g/GHMzSfJjjXyAqrZY2itam/IJd8r9FoRAzevPt/zTX -BET3INoiCDGRH0XrxUYtAgMGVTejggE5MIIBNTAMBgNVHRMEBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdHxf -FKXipZLjs20GqIUdNkQXH4gwgagGA1UdIwSBoDCBnYAUs7zqWHSr4W54KrKrnCMe -qGMsl7ehgYGkfzB9MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jw -b3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5n -IFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW+CAQEwMwYJ -YIZIAYb4QgEEBCYWJGh0dHA6Ly9hZGRvbnMubW96aWxsYS5vcmcvY2EvY3JsLnBl -bTANBgkqhkiG9w0BAQwFAAOCAgEArde/fdjb7TE0eH7Ij7xU4JbcSyhY3cQhVYCw -Fg+Q/2pj+NAfazcjUuLWA0Y/YZs9HOx6j+ZAqO4C/xfMP4RDs9IypxvzHDU6SXgD -RK6uOKtS07HXLcXgFUBvJEQhbT/h5+IQOA4/GcpCshfD6iyiBBi+IocR+tnKPCuZ -T3m1t60Eja/MkPKG/Gx8vSodHvlTTsJ2GzjUEANveCZOnlAdp9fjTvFZny9qqnbg -sfVbuTqKndbCFW5QLXfkna6jBqMrY0+CpMYY2oJ5gwpHbE/7hhukjxGCTcpv7r/O -M53bb/DZnybDlLLepacljvz7DBA1O1FFtEhf9MR+vyvmBpniAyKQhqG2hsVGurE1 -nBcE+oteZWar2lMp6+etDAb9DRC+jZv0aEQs2o/qQwyD8AGquLgBsJq5Jz3gGxzn -4r3vGu2lV8VdzIm0C8sOFSWTmTZxQmJbF8xSsQBojnsvEah4DPER+eAt6qKolaWe -s4drJQjzFyC7HJn2VqalpCwbe9CdMB7eRqzeP6GujJBi80/gx0pAysUtuKKpH5IJ -WbXAOszfrjb3CaHafYZDnwPoOfj74ogFzjt2f54jwnU+ET/byfjZ7J8SLH316C1V -HrvFXcTzyMV4aRluVPjPg9x1G58hMIbeuT4GpwQUNdJ9uL8t65v0XwG2t6Y7jpRO -sFVxBtgxggNXMIIDUwIBATCB0DCBxTELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01v -emlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rp -b24gU2lnbmluZyBTZXJ2aWNlMTEwLwYDVQQDEyhwcm9kdWN0aW9uLXNpZ25pbmct -Y2EuYWRkb25zLm1vemlsbGEub3JnMTQwMgYJKoZIhvcNAQkBFiVzZXJ2aWNlcy1v -cHMrYWRkb25zaWduaW5nQG1vemlsbGEuY29tAgYBVpobWVwwCQYFKw4DAhoFAKBd -MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE2MDgx -NzIwMDQ1OFowIwYJKoZIhvcNAQkEMRYEFAxlGvNFSx+Jqj70haE8b7UZk+2GMA0G -CSqGSIb3DQEBAQUABIICADsDlrucYRgwq9o2QSsO6X6cRa5Zu6w+1n07PTIyc1zn -Pi1cgkkWZ0kZBHDrJ5CY33yRQPl6I1tHXaq7SkOSdOppKhpUmBiKZxQRAZR21QHk -R3v1XS+st/o0N+0btv3YoplUifLIwtH89oolxqlStChELu7FuOBretdhx/z12ytA -EhIIS53o/XjDL7XKJbQA02vzOtOC/Eq6p8BI7F3y6pvtmJIRkeGv+u6ssJa6g5q8 -74w8hHXaH94Z9+hDPqjNWlsXJHgPdAKiEjzDz9oLkvDyX4Pd8JMK5ILskirpG+hj -Q8jkTc5oYwyuSlBAUTGxW6ZbuOrtfVZvOVtRL/ixuiFiVlJ+JOQOxrtK19ukamsI -iacFlbLgiA7w0HCtm2DsT9aL67/1e4rJ0lv0MjnQYUMmKQy7g0Gd3+nQPU9pn+Lf -Z/UmSNWiJ8Csc/seDMyzT6jrzcGPfoSVaUowH0wGrI9If1snwcr+mMg7dWRGf1fm -y/dcVSzed0ax4LqDmike1EshU+51cKWWlnhyNHK4KH+0fNsBQ0c6clrFpGx9MPmV -YXie6C+LWkh5x12RU0sJt/SmSZV6q9VliIkX+yY3jBrC/pKgRahtcIyq46Da1E6K -lc15Euur3NfGow+nott0Z8XutpYdK/2vBKcIh9JOdkd+oe6pcIP6hnhHRp53wqmG ------END PKCS7-----` - -var FirefoxAddonRootCert = []byte(` ------BEGIN CERTIFICATE----- -MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0GA1UECxMmTW96aWxsYSBB -TU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAdBgNVBAMTFnJvb3QtY2Et -cHJvZHVjdGlvbi1hbW8wHhcNMTUwMzE3MjI1MzU3WhcNMjUwMzE0MjI1MzU3WjB9 -MQswCQYDVQQGEwJVUzEcMBoGA1UEChMTTW96aWxsYSBDb3Jwb3JhdGlvbjEvMC0G -A1UECxMmTW96aWxsYSBBTU8gUHJvZHVjdGlvbiBTaWduaW5nIFNlcnZpY2UxHzAd -BgNVBAMTFnJvb3QtY2EtcHJvZHVjdGlvbi1hbW8wggIgMA0GCSqGSIb3DQEBAQUA -A4ICDQAwggIIAoICAQC0u2HXXbrwy36+MPeKf5jgoASMfMNz7mJWBecJgvlTf4hH -JbLzMPsIUauzI9GEpLfHdZ6wzSyFOb4AM+D1mxAWhuZJ3MDAJOf3B1Rs6QorHrl8 -qqlNtPGqepnpNJcLo7JsSqqE3NUm72MgqIHRgTRsqUs+7LIPGe7262U+N/T0LPYV -Le4rZ2RDHoaZhYY7a9+49mHOI/g2YFB+9yZjE+XdplT2kBgA4P8db7i7I0tIi4b0 -B0N6y9MhL+CRZJyxdFe2wBykJX14LsheKsM1azHjZO56SKNrW8VAJTLkpRxCmsiT -r08fnPyDKmaeZ0BtsugicdipcZpXriIGmsZbI12q5yuwjSELdkDV6Uajo2n+2ws5 -uXrP342X71WiWhC/dF5dz1LKtjBdmUkxaQMOP/uhtXEKBrZo1ounDRQx1j7+SkQ4 -BEwjB3SEtr7XDWGOcOIkoJZWPACfBLC3PJCBWjTAyBlud0C5n3Cy9regAAnOIqI1 -t16GU2laRh7elJ7gPRNgQgwLXeZcFxw6wvyiEcmCjOEQ6PM8UQjthOsKlszMhlKw -vjyOGDoztkqSBy/v+Asx7OW2Q7rlVfKarL0mREZdSMfoy3zTgtMVCM0vhNl6zcvf -5HNNopoEdg5yuXo2chZ1p1J+q86b0G5yJRMeT2+iOVY2EQ37tHrqUURncCy4uwIB -A6OB7TCB6jAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8E -DDAKBggrBgEFBQcDAzCBkgYDVR0jBIGKMIGHoYGBpH8wfTELMAkGA1UEBhMCVVMx -HDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAtBgNVBAsTJk1vemlsbGEg -QU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMR8wHQYDVQQDExZyb290LWNh -LXByb2R1Y3Rpb24tYW1vggEBMB0GA1UdDgQWBBSzvOpYdKvhbngqsqucIx6oYyyX -tzANBgkqhkiG9w0BAQwFAAOCAgEAaNSRYAaECAePQFyfk12kl8UPLh8hBNidP2H6 -KT6O0vCVBjxmMrwr8Aqz6NL+TgdPmGRPDDLPDpDJTdWzdj7khAjxqWYhutACTew5 -eWEaAzyErbKQl+duKvtThhV2p6F6YHJ2vutu4KIciOMKB8dslIqIQr90IX2Usljq -8Ttdyf+GhUmazqLtoB0GOuESEqT4unX6X7vSGu1oLV20t7t5eCnMMYD67ZBn0YIU -/cm/+pan66hHrja+NeDGF8wabJxdqKItCS3p3GN1zUGuJKrLykxqbOp/21byAGog -Z1amhz6NHUcfE6jki7sM7LHjPostU5ZWs3PEfVVgha9fZUhOrIDsyXEpCWVa3481 -LlAq3GiUMKZ5DVRh9/Nvm4NwrTfB3QkQQJCwfXvO9pwnPKtISYkZUqhEqvXk5nBg -QCkDSLDjXTx39naBBGIVIqBtKKuVTla9enngdq692xX/CgO6QJVrwpqdGjebj5P8 -5fNZPABzTezG3Uls5Vp+4iIWVAEDkK23cUj3c/HhE+Oo7kxfUeu5Y1ZV3qr61+6t -ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL -26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT -wNuvFqc= ------END CERTIFICATE-----`) - -// sign a document with openssl and verify the signature with pkcs7. -// this uses a chain of root, intermediate and signer cert, where the -// intermediate is added to the certs but the root isn't. -func TestSignWithOpenSSLAndVerify(t *testing.T) { - content := []byte(` -A ship in port is safe, -but that's not what ships are built for. --- Grace Hopper`) - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - sigalgs := []x509.SignatureAlgorithm{ - x509.SHA1WithRSA, - x509.SHA256WithRSA, - x509.SHA512WithRSA, - x509.ECDSAWithSHA1, - x509.ECDSAWithSHA256, - x509.ECDSAWithSHA384, - x509.ECDSAWithSHA512, - } - for _, sigalgroot := range sigalgs { - rootCert, err := createTestCertificateByIssuer("PKCS7 Test Root CA", nil, sigalgroot, true) - if err != nil { - t.Fatalf("test %s: cannot generate root cert: %s", sigalgroot, err) - } - truststore := x509.NewCertPool() - truststore.AddCert(rootCert.Certificate) - for _, sigalginter := range sigalgs { - interCert, err := createTestCertificateByIssuer("PKCS7 Test Intermediate Cert", rootCert, sigalginter, true) - if err != nil { - t.Fatalf("test %s/%s: cannot generate intermediate cert: %s", sigalgroot, sigalginter, err) - } - // write the intermediate cert to a temp file - tmpInterCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_intermediate") - if err != nil { - t.Fatal(err) - } - fd, err := os.OpenFile(tmpInterCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: interCert.Certificate.Raw}) - fd.Close() - for _, sigalgsigner := range sigalgs { - signerCert, err := createTestCertificateByIssuer("PKCS7 Test Signer Cert", interCert, sigalgsigner, false) - if err != nil { - t.Fatalf("test %s/%s/%s: cannot generate signer cert: %s", sigalgroot, sigalginter, sigalgsigner, err) - } - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signer") - if err != nil { - t.Fatal(err) - } - fd, err = os.OpenFile(tmpSignerCertFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Certificate.Raw}) - fd.Close() - - // write the signer key to a temp file - tmpSignerKeyFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_key") - if err != nil { - t.Fatal(err) - } - fd, err = os.OpenFile(tmpSignerKeyFile.Name(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - t.Fatal(err) - } - var derKey []byte - priv := *signerCert.PrivateKey - switch priv := priv.(type) { - case *rsa.PrivateKey: - derKey = x509.MarshalPKCS1PrivateKey(priv) - pem.Encode(fd, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: derKey}) - case *ecdsa.PrivateKey: - derKey, err = x509.MarshalECPrivateKey(priv) - if err != nil { - t.Fatal(err) - } - pem.Encode(fd, &pem.Block{Type: "EC PRIVATE KEY", Bytes: derKey}) - } - fd.Close() - - // write the root cert to a temp file - tmpSignedFile, err := ioutil.TempFile("", "TestSignWithOpenSSLAndVerify_signature") - if err != nil { - t.Fatal(err) - } - // call openssl to sign the content - opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", - "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), - "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), - "-certfile", tmpInterCertFile.Name(), "-outform", "PEM") - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("test %s/%s/%s: openssl command failed with %s: %s", sigalgroot, sigalginter, sigalgsigner, err, out) - } - - // verify the signed content - pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) - if err != nil { - t.Fatal(err) - } - derBlock, _ := pem.Decode(pemSignature) - if derBlock == nil { - break - } - p7, err := Parse(derBlock.Bytes) - if err != nil { - t.Fatalf("Parse encountered unexpected error: %v", err) - } - if err := p7.VerifyWithChain(truststore); err != nil { - t.Fatalf("Verify failed with error: %v", err) - } - // Verify the certificate chain to make sure the identified root - // is the one we expect - ee := getCertFromCertsByIssuerAndSerial(p7.Certificates, p7.Signers[0].IssuerAndSerialNumber) - if ee == nil { - t.Fatalf("No end-entity certificate found for signer") - } - chains, err := verifyCertChain(ee, p7.Certificates, truststore, time.Now()) - if err != nil { - t.Fatal(err) - } - if len(chains) != 1 { - t.Fatalf("Expected to find one chain, but found %d", len(chains)) - } - if len(chains[0]) != 3 { - t.Fatalf("Expected to find three certificates in chain, but found %d", len(chains[0])) - } - if chains[0][0].Subject.CommonName != "PKCS7 Test Signer Cert" { - t.Fatalf("Expected to find EE certificate with subject 'PKCS7 Test Signer Cert', but found '%s'", chains[0][0].Subject.CommonName) - } - if chains[0][1].Subject.CommonName != "PKCS7 Test Intermediate Cert" { - t.Fatalf("Expected to find intermediate certificate with subject 'PKCS7 Test Intermediate Cert', but found '%s'", chains[0][1].Subject.CommonName) - } - if chains[0][2].Subject.CommonName != "PKCS7 Test Root CA" { - t.Fatalf("Expected to find root certificate with subject 'PKCS7 Test Root CA', but found '%s'", chains[0][2].Subject.CommonName) - } - os.Remove(tmpSignerCertFile.Name()) // clean up - os.Remove(tmpSignerKeyFile.Name()) // clean up - } - os.Remove(tmpInterCertFile.Name()) // clean up - } - } - os.Remove(tmpContentFile.Name()) // clean up -} - -func TestDSASignWithOpenSSLAndVerify(t *testing.T) { - content := []byte(` -A ship in port is safe, -but that's not what ships are built for. --- Grace Hopper`) - // write the content to a temp file - tmpContentFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_content") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpContentFile.Name(), content, 0755) - - // write the signer cert to a temp file - tmpSignerCertFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signer") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerCertFile.Name(), dsaPublicCert, 0755) - - // write the signer key to a temp file - tmpSignerKeyFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_key") - if err != nil { - t.Fatal(err) - } - ioutil.WriteFile(tmpSignerKeyFile.Name(), dsaPrivateKey, 0755) - - tmpSignedFile, err := ioutil.TempFile("", "TestDSASignWithOpenSSLAndVerify_signature") - if err != nil { - t.Fatal(err) - } - // call openssl to sign the content - opensslCMD := exec.Command("openssl", "smime", "-sign", "-nodetach", "-md", "sha1", - "-in", tmpContentFile.Name(), "-out", tmpSignedFile.Name(), - "-signer", tmpSignerCertFile.Name(), "-inkey", tmpSignerKeyFile.Name(), - "-certfile", tmpSignerCertFile.Name(), "-outform", "PEM") - out, err := opensslCMD.CombinedOutput() - if err != nil { - t.Fatalf("openssl command failed with %s: %s", err, out) - } - - // verify the signed content - pemSignature, err := ioutil.ReadFile(tmpSignedFile.Name()) - if err != nil { - t.Fatal(err) - } - fmt.Printf("%s\n", pemSignature) - derBlock, _ := pem.Decode(pemSignature) - if derBlock == nil { - t.Fatalf("failed to read DER block from signature PEM %s", tmpSignedFile.Name()) - } - p7, err := Parse(derBlock.Bytes) - if err != nil { - t.Fatalf("Parse encountered unexpected error: %v", err) - } - if err := p7.Verify(); err != nil { - t.Fatalf("Verify failed with error: %v", err) - } - os.Remove(tmpSignerCertFile.Name()) // clean up - os.Remove(tmpSignerKeyFile.Name()) // clean up - os.Remove(tmpContentFile.Name()) // clean up -} - -var dsaPrivateKey = []byte(`-----BEGIN PRIVATE KEY----- -MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdS -PO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVCl -pJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith -1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7L -vKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3 -zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImo -g9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUfW4aPdQBn9gJZp2KuNpzgHzvfsE= ------END PRIVATE KEY-----`) - -var dsaPublicCert = []byte(`-----BEGIN CERTIFICATE----- -MIIDOjCCAvWgAwIBAgIEPCY/UDANBglghkgBZQMEAwIFADBsMRAwDgYDVQQGEwdV -bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD -VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du -MB4XDTE4MTAyMjEzNDMwN1oXDTQ2MDMwOTEzNDMwN1owbDEQMA4GA1UEBhMHVW5r -bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE -ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC -AbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3UjzvRADD -Hj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gE -exAiwk+7qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/Ii -Axmd0UgBxwIVAJdgUI8VIwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4 -V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrUWU/mcQcQgYC0SRZxI+hMKBYTt88JMozI -puE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEkO8yk8b6oUZCJqIPf4Vrl -nwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDCriMPbEVBoRK4SOUeFwg7+VRf4TTp -rcOQC9IVVoCjXzuWEGrp3ZI7YWJSpFnSch4lk29RH8O0HpI/NOzKnOBtnKr782pt -1k/bJVMH9EaLd6MKnAVjrCDMYBB0MhebZ8QHY2elZZCWoqDYAcIDOsEx+m4NLErT -ypPnjS5M0jm1PKMhMB8wHQYDVR0OBBYEFC0Yt5XdM0Kc95IX8NQ8XRssGPx7MA0G -CWCGSAFlAwQDAgUAAzAAMC0CFQCIgQtrZZ9hdZG1ROhR5hc8nYEmbgIUAIlgC688 -qzy/7yePTlhlpj+ahMM= ------END CERTIFICATE-----`) diff --git a/scep/scep.go b/scep/scep.go index bc46cce7..6a636aee 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -6,7 +6,9 @@ import ( microscep "github.com/micromdm/scep/scep" - "github.com/smallstep/certificates/scep/pkcs7" + //"github.com/smallstep/certificates/scep/pkcs7" + + "go.mozilla.org/pkcs7" ) // SCEP OIDs From 43229335886f8dd83a89e536bd4e964b066647bc Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 12:32:43 +0100 Subject: [PATCH 09/42] Add handling of options --- scep/api/api.go | 35 ++++++++++++----------------------- scep/authority.go | 40 +++++++++++++++++++++++----------------- scep/common.go | 22 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 scep/common.go diff --git a/scep/api/api.go b/scep/api/api.go index ec959519..c73bb53a 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -235,12 +235,12 @@ func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { - ctx := r.Context() + //ctx := r.Context() - _, err := ProvisionerFromContext(ctx) - if err != nil { - return err - } + // _, err := ProvisionerFromContext(ctx) + // if err != nil { + // return err + // } // TODO: get the actual capabilities from provisioner config scepResponse.Data = formatCapabilities(defaultCapabilities) @@ -250,6 +250,8 @@ func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { + ctx := r.Context() + msg, err := microscep.ParsePKIMessage(scepRequest.Message) if err != nil { return err @@ -262,7 +264,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque Raw: msg.Raw, } - if err := h.Auth.DecryptPKIEnvelope(pkimsg); err != nil { + if err := h.Auth.DecryptPKIEnvelope(ctx, pkimsg); err != nil { return err } @@ -271,7 +273,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque } csr := pkimsg.CSRReqMessage.CSR - id, err := createKeyIdentifier(csr.PublicKey) + subjectKeyID, err := createKeyIdentifier(csr.PublicKey) if err != nil { return err } @@ -286,16 +288,17 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque Subject: csr.Subject, NotBefore: time.Now().Add(-600).UTC(), NotAfter: time.Now().AddDate(0, 0, days).UTC(), - SubjectKeyId: id, + SubjectKeyId: subjectKeyID, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{ x509.ExtKeyUsageClientAuth, }, SignatureAlgorithm: csr.SignatureAlgorithm, EmailAddresses: csr.EmailAddresses, + DNSNames: csr.DNSNames, } - certRep, err := h.Auth.SignCSR(pkimsg, template) + certRep, err := h.Auth.SignCSR(ctx, pkimsg, template) if err != nil { return err } @@ -381,17 +384,3 @@ func contentHeader(operation string, certNum int) string { return "text/plain" } } - -// ProvisionerFromContext searches the context for a provisioner. Returns the -// provisioner or an error. -func ProvisionerFromContext(ctx context.Context) (scep.Provisioner, error) { - val := ctx.Value(acme.ProvisionerContextKey) - if val == nil { - return nil, errors.New("provisioner expected in request context") - } - p, ok := val.(scep.Provisioner) - if !ok || p == nil { - return nil, errors.New("provisioner in context is not a SCEP provisioner") - } - return p, nil -} diff --git a/scep/authority.go b/scep/authority.go index 864ecbba..cb191d34 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -2,6 +2,7 @@ package scep import ( "bytes" + "context" "crypto/x509" "errors" "fmt" @@ -54,8 +55,8 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) //GetSigningKey() (*rsa.PrivateKey, error) - DecryptPKIEnvelope(*PKIMessage) error - SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) + DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error + SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -156,7 +157,7 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { } // DecryptPKIEnvelope decrypts an enveloped message -func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { +func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error { data := msg.Raw @@ -221,14 +222,19 @@ func (a *Authority) DecryptPKIEnvelope(msg *PKIMessage) error { return nil } -// SignCSR creates an x509.Certificate based on a template and Cert Authority credentials +// SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials // returns a new PKIMessage with CertRep data //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { -func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return nil, err + } // check if CSRReqMessage has already been decrypted if msg.CSRReqMessage.CSR == nil { - if err := a.DecryptPKIEnvelope(msg); err != nil { + if err := a.DecryptPKIEnvelope(ctx, msg); err != nil { return nil, err } } @@ -238,13 +244,17 @@ func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMe // Template data data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) - //data.Set(x509util.SANsKey, sans) + data.SetSANs(csr.DNSNames) - // templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) - // if err != nil { - // return nil, ServerInternalErr(errors.Wrapf(err, "error creating template options from ACME provisioner")) - // } - // signOps = append(signOps, templateOptions) + // TODO: proper options + opts := provisioner.SignOptions{} + signOps := []provisioner.SignOption{} + + templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) + if err != nil { + return nil, fmt.Errorf("error creating template options from SCEP provisioner") + } + signOps = append(signOps, templateOptions) // // Create and store a new certificate. // certChain, err := auth.Sign(csr, provisioner.SignOptions{ @@ -255,11 +265,7 @@ func (a *Authority) SignCSR(msg *PKIMessage, template *x509.Certificate) (*PKIMe // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) // } - // TODO: proper options - signOps := provisioner.SignOptions{} - signOps2 := []provisioner.SignOption{} - - certs, err := a.signAuth.Sign(csr, signOps, signOps2...) + certs, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { return nil, err } diff --git a/scep/common.go b/scep/common.go new file mode 100644 index 00000000..123c8d82 --- /dev/null +++ b/scep/common.go @@ -0,0 +1,22 @@ +package scep + +import ( + "context" + "errors" + + "github.com/smallstep/certificates/acme" +) + +// ProvisionerFromContext searches the context for a SCEP provisioner. +// Returns the provisioner or an error. +func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { + val := ctx.Value(acme.ProvisionerContextKey) + if val == nil { + return nil, errors.New("provisioner expected in request context") + } + p, ok := val.(Provisioner) + if !ok || p == nil { + return nil, errors.New("provisioner in context is not a SCEP provisioner") + } + return p, nil +} From 311c9d767bc72582bfbf5d74e6b5d75a51711fee Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 14:00:47 +0100 Subject: [PATCH 10/42] Add AuthorizeSign method to SCEP authority --- authority/provisioner/scep.go | 18 ++++++++++++- scep/authority.go | 48 +++++++++++++++++------------------ scep/provisioner.go | 3 ++- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 6cdfa69f..10414b5e 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -1,6 +1,7 @@ package provisioner import ( + "context" "time" "github.com/pkg/errors" @@ -13,7 +14,7 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - // ForceCN bool `json:"forceCN,omitempty"` + ForceCN bool `json:"forceCN,omitempty"` Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer @@ -75,6 +76,21 @@ func (s *SCEP) Init(config Config) (err error) { return err } +// AuthorizeSign does not do any validation, because all validation is handled +// in the SCEP protocol. This method returns a list of modifiers / constraints +// on the resulting certificate. +func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { + return []SignOption{ + // modifiers / withOptions + newProvisionerExtensionOption(TypeSCEP, s.Name, ""), + newForceCNOption(s.ForceCN), + profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), + // validators + defaultPublicKeyValidator{}, + newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), + }, nil +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/authority.go b/scep/authority.go index cb191d34..c9a2cdb8 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -6,8 +6,6 @@ import ( "crypto/x509" "errors" "fmt" - "math/big" - "math/rand" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" @@ -53,8 +51,6 @@ type Interface interface { // GetLinkExplicit(linkType Link, provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) - //GetSigningKey() (*rsa.PrivateKey, error) - DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) } @@ -201,12 +197,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("parse CSR from pkiEnvelope") + return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) } // check for challengePassword cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("scep: parse challenge password in pkiEnvelope") + return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err) } msg.CSRReqMessage = µscep.CSRReqMessage{ RawDecrypted: msg.pkiEnvelope, @@ -227,6 +223,11 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { + // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate + // to be signed, which can be performed asynchronously / out-of-band. In that case a client can + // poll for the status. It seems to be similar as what can happen in ACME, so might want to model + // the implementation after the one in the ACME authority. Requires storage, etc. + p, err := ProvisionerFromContext(ctx) if err != nil { return nil, err @@ -245,32 +246,32 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) data.SetSANs(csr.DNSNames) + data.SetCertificateRequest(csr) - // TODO: proper options - opts := provisioner.SignOptions{} - signOps := []provisioner.SignOption{} + // Get authorizations from the SCEP provisioner. + ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) + signOps, err := p.AuthorizeSign(ctx, "") + if err != nil { + return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) + } + + opts := provisioner.SignOptions{ + // NotBefore: provisioner.NewTimeDuration(o.NotBefore), + // NotAfter: provisioner.NewTimeDuration(o.NotAfter), + } templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) if err != nil { - return nil, fmt.Errorf("error creating template options from SCEP provisioner") + return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err) } signOps = append(signOps, templateOptions) - // // Create and store a new certificate. - // certChain, err := auth.Sign(csr, provisioner.SignOptions{ - // NotBefore: provisioner.NewTimeDuration(o.NotBefore), - // NotAfter: provisioner.NewTimeDuration(o.NotAfter), - // }, signOps...) - // if err != nil { - // return nil, ServerInternalErr(errors.Wrapf(err, "error generating certificate for order %s", o.ID)) - // } - - certs, err := a.signAuth.Sign(csr, opts, signOps...) + certChain, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { - return nil, err + return nil, fmt.Errorf("error generating certificate for order %w", err) } - cert := certs[0] + cert := certChain[0] // fmt.Println("CERT") // fmt.Println(cert) @@ -278,9 +279,6 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 // fmt.Println(cert.Issuer) // fmt.Println(cert.Subject) - serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - cert.SerialNumber = serial - // create a degenerate cert structure deg, err := DegenerateCertificates([]*x509.Certificate{cert}) if err != nil { diff --git a/scep/provisioner.go b/scep/provisioner.go index d543d453..64a787d4 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -1,6 +1,7 @@ package scep import ( + "context" "time" "github.com/smallstep/certificates/authority/provisioner" @@ -9,7 +10,7 @@ import ( // 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) + AuthorizeSign(ctx context.Context, token string) ([]provisioner.SignOption, error) GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options From a6d50f2fa0713f31900093d5f34ca9c8bcaaaafe Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Feb 2021 18:07:50 +0100 Subject: [PATCH 11/42] Remove x509 template from API --- scep/api/api.go | 64 ++++++++--------------------------------------- scep/authority.go | 48 ++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index c73bb53a..16278075 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -2,19 +2,14 @@ package api 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" @@ -252,53 +247,29 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque ctx := r.Context() - msg, err := microscep.ParsePKIMessage(scepRequest.Message) + microMsg, err := microscep.ParsePKIMessage(scepRequest.Message) if err != nil { return err } - pkimsg := &scep.PKIMessage{ - TransactionID: msg.TransactionID, - MessageType: msg.MessageType, - SenderNonce: msg.SenderNonce, - Raw: msg.Raw, + msg := &scep.PKIMessage{ + TransactionID: microMsg.TransactionID, + MessageType: microMsg.MessageType, + SenderNonce: microMsg.SenderNonce, + Raw: microMsg.Raw, } - if err := h.Auth.DecryptPKIEnvelope(ctx, pkimsg); err != nil { + if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { return err } - if pkimsg.MessageType == microscep.PKCSReq { + if msg.MessageType == microscep.PKCSReq { // TODO: CSR validation, like challenge password } - csr := pkimsg.CSRReqMessage.CSR - subjectKeyID, err := createKeyIdentifier(csr.PublicKey) - if err != nil { - return err - } + csr := msg.CSRReqMessage.CSR - serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - - days := 40 - - // TODO: use information from provisioner, like claims - template := &x509.Certificate{ - SerialNumber: serial, - Subject: csr.Subject, - NotBefore: time.Now().Add(-600).UTC(), - NotAfter: time.Now().AddDate(0, 0, days).UTC(), - SubjectKeyId: subjectKeyID, - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - }, - SignatureAlgorithm: csr.SignatureAlgorithm, - EmailAddresses: csr.EmailAddresses, - DNSNames: csr.DNSNames, - } - - certRep, err := h.Auth.SignCSR(ctx, pkimsg, template) + certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { return err } @@ -323,20 +294,6 @@ func certName(cert *x509.Certificate) string { return string(cert.Signature) } -// createKeyIdentifier creates 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, "\r\n")) } @@ -354,6 +311,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) error { 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", diff --git a/scep/authority.go b/scep/authority.go index c9a2cdb8..013550a6 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -3,6 +3,8 @@ package scep import ( "bytes" "context" + "crypto" + "crypto/sha1" "crypto/x509" "errors" "fmt" @@ -52,7 +54,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error - SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) + SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) } // Authority is the layer that handles all SCEP interactions. @@ -221,7 +223,8 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err // SignCSR creates an x509.Certificate based on a CSR template and Cert Authority credentials // returns a new PKIMessage with CertRep data //func (msg *PKIMessage) SignCSR(crtAuth *x509.Certificate, keyAuth *rsa.PrivateKey, template *x509.Certificate) (*PKIMessage, error) { -func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +//func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509.Certificate) (*PKIMessage, error) { +func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) { // TODO: intermediate storage of the request? In SCEP it's possible to request a csr/certificate // to be signed, which can be performed asynchronously / out-of-band. In that case a client can @@ -238,9 +241,32 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 if err := a.DecryptPKIEnvelope(ctx, msg); err != nil { return nil, err } + csr = msg.CSRReqMessage.CSR } - csr := msg.CSRReqMessage.CSR + // subjectKeyID, err := createKeyIdentifier(csr.PublicKey) + // if err != nil { + // return nil, err + // } + + // serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? + // days := 40 // TODO: days + + // // TODO: use information from provisioner, like claims + // template := &x509.Certificate{ + // SerialNumber: serial, + // Subject: csr.Subject, + // NotBefore: time.Now().Add(-600).UTC(), + // NotAfter: time.Now().AddDate(0, 0, days).UTC(), + // SubjectKeyId: subjectKeyID, + // KeyUsage: x509.KeyUsageDigitalSignature, + // ExtKeyUsage: []x509.ExtKeyUsage{ + // x509.ExtKeyUsageClientAuth, + // }, + // SignatureAlgorithm: csr.SignatureAlgorithm, + // EmailAddresses: csr.EmailAddresses, + // DNSNames: csr.DNSNames, + // } // Template data data := x509util.NewTemplateData() @@ -278,6 +304,8 @@ func (a *Authority) SignCSR(ctx context.Context, msg *PKIMessage, template *x509 // fmt.Println(fmt.Sprintf("%T", cert)) // fmt.Println(cert.Issuer) // fmt.Println(cert.Subject) + // fmt.Println(cert.SerialNumber) + // fmt.Println(string(cert.SubjectKeyId)) // create a degenerate cert structure deg, err := DegenerateCertificates([]*x509.Certificate{cert}) @@ -365,6 +393,20 @@ func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { return degenerate, nil } +// createKeyIdentifier creates 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 +} + // Interface guards var ( _ Interface = (*Authority)(nil) From 2fc5a7f22e1f0c95ad8afcd07e795cba39ff643c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 27 Feb 2021 00:34:50 +0100 Subject: [PATCH 12/42] Improve SCEP API logic and error handling --- api/errors.go | 4 + scep/api/api.go | 215 +++++++++++++++++++++++++----------------------- scep/common.go | 11 ++- scep/errors.go | 19 +++++ 4 files changed, 146 insertions(+), 103 deletions(-) create mode 100644 scep/errors.go diff --git a/api/errors.go b/api/errors.go index 93057ed2..3e5dec47 100644 --- a/api/errors.go +++ b/api/errors.go @@ -10,6 +10,7 @@ import ( "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/logging" + "github.com/smallstep/certificates/scep" ) // WriteError writes to w a JSON representation of the given error. @@ -18,6 +19,9 @@ func WriteError(w http.ResponseWriter, err error) { case *acme.Error: w.Header().Set("Content-Type", "application/problem+json") err = k.ToACME() + case *scep.Error: + // TODO: check if this is correct; and should we do some more processing? + w.Header().Set("Content-Type", "text/plain") default: w.Header().Set("Content-Type", "application/json") } diff --git a/scep/api/api.go b/scep/api/api.go index 16278075..f2d11fb7 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -11,7 +11,6 @@ import ( "net/http" "strings" - "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" @@ -27,6 +26,30 @@ const ( // TODO: add other (more optional) operations and handling ) +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" + pkiOpHeader = "application/x-pki-message" +) + // SCEPRequest is a SCEP server request. type SCEPRequest struct { Operation string @@ -35,10 +58,10 @@ type SCEPRequest struct { // SCEPResponse is a SCEP server response. type SCEPResponse struct { - Operation string - CACertNum int - Data []byte - Err error + Operation string + CACertNum int + Data []byte + Certificate *x509.Certificate } // Handler is the SCEP request handler. @@ -64,60 +87,65 @@ func (h *Handler) Route(r api.Router) { } +// Get handles all SCEP GET requests func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { - scepRequest, err := decodeSCEPRequest(r) + request, err := decodeSCEPRequest(r) if err != nil { - fmt.Println(err) - fmt.Println("not a scep get request") - w.WriteHeader(500) + writeError(w, fmt.Errorf("not a scep get request: %w", err)) + return } - scepResponse := SCEPResponse{Operation: scepRequest.Operation} + ctx := r.Context() + var response SCEPResponse - switch scepRequest.Operation { + switch request.Operation { case opnGetCACert: - err := h.GetCACert(w, r, scepResponse) - if err != nil { - fmt.Println(err) - } - + response, err = h.GetCACert(ctx) case opnGetCACaps: - err := h.GetCACaps(w, r, scepResponse) - if err != nil { - fmt.Println(err) - } + response, err = h.GetCACaps(ctx) case opnPKIOperation: - + // TODO: implement the GET for PKI operation default: - + err = fmt.Errorf("unknown operation: %s", request.Operation) } + + if err != nil { + writeError(w, fmt.Errorf("get request failed: %w", err)) + return + } + + writeSCEPResponse(w, response) } +// Post handles all SCEP POST requests func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { - scepRequest, err := decodeSCEPRequest(r) + request, err := decodeSCEPRequest(r) if err != nil { - fmt.Println(err) - fmt.Println("not a scep post request") - w.WriteHeader(500) + writeError(w, fmt.Errorf("not a scep post request: %w", err)) + return } - scepResponse := SCEPResponse{Operation: scepRequest.Operation} + ctx := r.Context() + var response SCEPResponse - switch scepRequest.Operation { + switch request.Operation { case opnPKIOperation: - err := h.PKIOperation(w, r, scepRequest, scepResponse) - if err != nil { - fmt.Println(err) - } + response, err = h.PKIOperation(ctx, request) default: - + err = fmt.Errorf("unknown operation: %s", request.Operation) } -} + if err != nil { + writeError(w, fmt.Errorf("post request failed: %w", err)) + return + } -const maxPayloadSize = 2 << 20 + api.LogCertificate(w, response.Certificate) + + writeSCEPResponse(w, response) +} func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { @@ -169,8 +197,6 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { } } -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 { @@ -189,67 +215,69 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { - api.WriteError(w, err) + writeError(w, err) return } - scepProvisioner, ok := p.(*provisioner.SCEP) + provisioner, ok := p.(*provisioner.SCEP) if !ok { - api.WriteError(w, errors.New("provisioner must be of type SCEP")) + writeError(w, errors.New("provisioner must be of type SCEP")) return } ctx := r.Context() - ctx = context.WithValue(ctx, acme.ProvisionerContextKey, scep.Provisioner(scepProvisioner)) + ctx = context.WithValue(ctx, scep.ProvisionerContextKey, scep.Provisioner(provisioner)) next(w, r.WithContext(ctx)) } } -func (h *Handler) GetCACert(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { +// GetCACert returns the CA certificates in a SCEP response +func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { certs, err := h.Auth.GetCACertificates() if err != nil { - return err + return SCEPResponse{}, err } 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.CACertNum = len(certs) - scepResponse.Data = data - scepResponse.Err = err + return SCEPResponse{}, errors.New("missing CA cert") } - return writeSCEPResponse(w, scepResponse) + response := SCEPResponse{Operation: opnGetCACert} + response.CACertNum = len(certs) + + if len(certs) == 1 { + response.Data = certs[0].Raw + } else { + data, err := microscep.DegenerateCertificates(certs) + if err != nil { + return SCEPResponse{}, err + } + response.Data = data + } + + return response, nil } -func (h *Handler) GetCACaps(w http.ResponseWriter, r *http.Request, scepResponse SCEPResponse) error { +// GetCACaps returns the CA capabilities in a SCEP response +func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { - //ctx := r.Context() - - // _, err := ProvisionerFromContext(ctx) - // if err != nil { - // return err - // } + response := SCEPResponse{Operation: opnGetCACaps} // TODO: get the actual capabilities from provisioner config - scepResponse.Data = formatCapabilities(defaultCapabilities) + response.Data = formatCapabilities(defaultCapabilities) - return writeSCEPResponse(w, scepResponse) + return response, nil } -func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepRequest SCEPRequest, scepResponse SCEPResponse) error { +// PKIOperation performs PKI operations and returns a SCEP response +func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { - ctx := r.Context() + response := SCEPResponse{Operation: opnPKIOperation} - microMsg, err := microscep.ParsePKIMessage(scepRequest.Message) + microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { - return err + return SCEPResponse{}, err } msg := &scep.PKIMessage{ @@ -260,7 +288,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque } if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { - return err + return SCEPResponse{}, err } if msg.MessageType == microscep.PKCSReq { @@ -271,7 +299,7 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return err + return SCEPResponse{}, err } // //cert := certRep.CertRepMessage.Certificate @@ -280,11 +308,10 @@ func (h *Handler) PKIOperation(w http.ResponseWriter, r *http.Request, scepReque // // 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; should go into the DB - scepResponse.Data = certRep.Raw + response.Data = certRep.Raw + response.Certificate = certRep.Certificate - api.LogCertificate(w, certRep.Certificate) - - return writeSCEPResponse(w, scepResponse) + return response, nil } func certName(cert *x509.Certificate) string { @@ -299,40 +326,26 @@ func formatCapabilities(caps []string) []byte { } // 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 +func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + w.Header().Set("Content-Type", contentHeader(response)) + _, err := w.Write(response.Data) + if err != nil { + writeError(w, fmt.Errorf("error when writing scep response: %w", err)) // This could end up as an error again } - w.Header().Set("Content-Type", contentHeader(response.Operation, response.CACertNum)) - w.Write(response.Data) - return nil } -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", +func writeError(w http.ResponseWriter, err error) { + scepError := &scep.Error{ + Err: fmt.Errorf("post request failed: %w", err), + Status: http.StatusInternalServerError, // TODO: make this a param? } -) + api.WriteError(w, scepError) +} -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 { +func contentHeader(r SCEPResponse) string { + switch r.Operation { case opnGetCACert: - if certNum > 1 { + if r.CACertNum > 1 { return certChainHeader } return leafHeader diff --git a/scep/common.go b/scep/common.go index 123c8d82..ca87841f 100644 --- a/scep/common.go +++ b/scep/common.go @@ -3,14 +3,21 @@ package scep import ( "context" "errors" +) - "github.com/smallstep/certificates/acme" +// ContextKey is the key type for storing and searching for SCEP request +// essentials in the context of a request. +type ContextKey string + +const ( + // ProvisionerContextKey provisioner key + ProvisionerContextKey = ContextKey("provisioner") ) // ProvisionerFromContext searches the context for a SCEP provisioner. // Returns the provisioner or an error. func ProvisionerFromContext(ctx context.Context) (Provisioner, error) { - val := ctx.Value(acme.ProvisionerContextKey) + val := ctx.Value(ProvisionerContextKey) if val == nil { return nil, errors.New("provisioner expected in request context") } diff --git a/scep/errors.go b/scep/errors.go new file mode 100644 index 00000000..52fff8ae --- /dev/null +++ b/scep/errors.go @@ -0,0 +1,19 @@ +package scep + +// Error is an SCEP error type +type Error struct { + // Type ProbType + // Detail string + Err error + Status int + // Sub []*Error + // Identifier *Identifier +} + +// Error implements the error interface. +func (e *Error) Error() string { + // if e.Err == nil { + // return e.Detail + // } + return e.Err.Error() +} From 3a5f633cdd1fd0c59c287849b0f9fc3764435d8c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 5 Mar 2021 12:40:42 +0100 Subject: [PATCH 13/42] Add support for multiple SCEP provisioners Similarly to how ACME suppors multiple provisioners, it's now possible to load the right provisioner based on the URL. --- ca/ca.go | 2 +- scep/api/api.go | 41 +++++++++++++++++++---------------- scep/authority.go | 55 ++++++++++++++++++++++++++++++++++++++++++++--- scep/errors.go | 6 +++--- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index d062cbef..7993ba38 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -174,7 +174,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepRouterHandler.Route(r) }) - // helpful routine for logging all routes // + // helpful routine for logging all routes //dumpRoutes(mux) // Add monitoring if configured diff --git a/scep/api/api.go b/scep/api/api.go index f2d11fb7..4df5d6a1 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -9,8 +9,10 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "strings" + "github.com/go-chi/chi" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" @@ -76,14 +78,10 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { - //getLink := h.Auth.GetLinkExplicit - //fmt.Println(getLink) + getLink := h.Auth.GetLinkExplicit - //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)) + r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) + r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) } @@ -202,16 +200,12 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { - // name := chi.URLParam(r, "provisionerID") - // provisionerID, err := url.PathUnescape(name) - // if err != nil { - // api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) - // return - // } - - // TODO: make this configurable; and we might want to look at being able to provide multiple, - // like the ACME one? The below assumes a SCEP provider (scep/) called "scep1" exists. - provisionerID := "scep1" + name := chi.URLParam(r, "provisionerID") + provisionerID, err := url.PathUnescape(name) + if err != nil { + api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + return + } p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { @@ -275,6 +269,8 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} + fmt.Println("BEFORE PARSING") + microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err @@ -287,7 +283,12 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe Raw: microMsg.Raw, } + fmt.Println("len raw:", len(microMsg.Raw)) + + fmt.Println("AFTER PARSING") + if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { + fmt.Println("ERROR IN DECRYPTPKIENVELOPE") return SCEPResponse{}, err } @@ -311,6 +312,8 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response.Data = certRep.Raw response.Certificate = certRep.Certificate + fmt.Println("HERE!!!") + return response, nil } @@ -336,8 +339,8 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { func writeError(w http.ResponseWriter, err error) { scepError := &scep.Error{ - Err: fmt.Errorf("post request failed: %w", err), - Status: http.StatusInternalServerError, // TODO: make this a param? + Message: err.Error(), + Status: http.StatusInternalServerError, // TODO: make this a param? } api.WriteError(w, scepError) } diff --git a/scep/authority.go b/scep/authority.go index 013550a6..e5d1ea48 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "errors" "fmt" + "net/url" "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" @@ -55,6 +56,8 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error SignCSR(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage) (*PKIMessage, error) + + GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } // Authority is the layer that handles all SCEP interactions. @@ -130,6 +133,44 @@ func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error return a.signAuth.LoadProvisionerByID(id) } +// GetLinkExplicit returns the requested link from the directory. +func (a *Authority) GetLinkExplicit(provName string, abs bool, baseURL *url.URL, inputs ...string) string { + // TODO: taken from ACME; move it to directory (if we need a directory in SCEP)? + return a.getLinkExplicit(provName, abs, baseURL, inputs...) +} + +// getLinkExplicit returns an absolute or partial path to the given resource and a base +// URL dynamically obtained from the request for which the link is being calculated. +func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string { + + // TODO: do we need to provide a way to provide a different suffix/base? + // Like "/cgi-bin/pkiclient.exe"? Or would it be enough to have that as the name? + link := fmt.Sprintf("/%s", provisionerName) + + if abs { + // Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351 + u := url.URL{} + if baseURL != nil { + u = *baseURL + } + + // If no Scheme is set, then default to https. + if u.Scheme == "" { + u.Scheme = "https" + } + + // If no Host is set, then use the default (first DNS attr in the ca.json). + if u.Host == "" { + u.Host = a.dns + } + + u.Path = a.prefix + link + return u.String() + } + + return link +} + // GetCACertificates returns the certificate (chain) for the CA func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { @@ -164,6 +205,8 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } + fmt.Println("len content:", len(p7.Content)) + var tID microscep.TransactionID if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { return err @@ -176,11 +219,17 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err msg.p7 = p7 + //p7c, err := pkcs7.Parse(p7.Content) p7c, err := pkcs7.Parse(p7.Content) if err != nil { return err } + fmt.Println(tID) + fmt.Println(msgType) + + fmt.Println("len p7c content:", len(p7c.Content)) + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) if err != nil { return err @@ -308,7 +357,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m // fmt.Println(string(cert.SubjectKeyId)) // create a degenerate cert structure - deg, err := DegenerateCertificates([]*x509.Certificate{cert}) + deg, err := degenerateCertificates([]*x509.Certificate{cert}) if err != nil { return nil, err } @@ -380,8 +429,8 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } -// DegenerateCertificates creates degenerate certificates pkcs#7 type -func DegenerateCertificates(certs []*x509.Certificate) ([]byte, error) { +// degenerateCertificates creates degenerate certificates pkcs#7 type +func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { var buf bytes.Buffer for _, cert := range certs { buf.Write(cert.Raw) diff --git a/scep/errors.go b/scep/errors.go index 52fff8ae..8454e16d 100644 --- a/scep/errors.go +++ b/scep/errors.go @@ -4,8 +4,8 @@ package scep type Error struct { // Type ProbType // Detail string - Err error - Status int + Message string `json:"message"` + Status int `json:"-"` // Sub []*Error // Identifier *Identifier } @@ -15,5 +15,5 @@ func (e *Error) Error() string { // if e.Err == nil { // return e.Detail // } - return e.Err.Error() + return e.Message } From 9df5f513e7ac4e55aa4f688c75be24f30e63a2fe Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 6 Mar 2021 22:35:41 +0100 Subject: [PATCH 14/42] Change to a fixed fork of go.mozilla.org/pkcs7 Hopefully this will be a temporary change until the fix is merged in the upstream module. --- go.mod | 3 +++ go.sum | 2 ++ scep/api/api.go | 13 ++----------- scep/authority.go | 7 ------- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 921067ea..f5f1e64d 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/nosql v0.3.6 github.com/urfave/cli v1.22.4 + go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 go.step.sm/cli-utils v0.1.0 go.step.sm/crypto v0.7.3 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 @@ -34,3 +35,5 @@ require ( // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto + +replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 diff --git a/go.sum b/go.sum index 59417e6a..ca8060a1 100644 --- a/go.sum +++ b/go.sum @@ -257,6 +257,8 @@ github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/I github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk= +github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/scep/api/api.go b/scep/api/api.go index 4df5d6a1..fc134a95 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -82,7 +82,6 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) - } // Get handles all SCEP GET requests @@ -103,7 +102,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { case opnGetCACaps: response, err = h.GetCACaps(ctx) case opnPKIOperation: - // TODO: implement the GET for PKI operation + // TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though default: err = fmt.Errorf("unknown operation: %s", request.Operation) } @@ -170,6 +169,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { if _, ok := query["message"]; ok { message = query.Get("message") } + // TODO: verify this; it seems like it should be StdEncoding instead of URLEncoding decodedMessage, err := base64.URLEncoding.DecodeString(message) if err != nil { return SCEPRequest{}, err @@ -269,8 +269,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} - fmt.Println("BEFORE PARSING") - microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err @@ -283,12 +281,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe Raw: microMsg.Raw, } - fmt.Println("len raw:", len(microMsg.Raw)) - - fmt.Println("AFTER PARSING") - if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { - fmt.Println("ERROR IN DECRYPTPKIENVELOPE") return SCEPResponse{}, err } @@ -312,8 +305,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response.Data = certRep.Raw response.Certificate = certRep.Certificate - fmt.Println("HERE!!!") - return response, nil } diff --git a/scep/authority.go b/scep/authority.go index e5d1ea48..a1d47700 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -205,8 +205,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - fmt.Println("len content:", len(p7.Content)) - var tID microscep.TransactionID if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { return err @@ -225,11 +223,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - fmt.Println(tID) - fmt.Println(msgType) - - fmt.Println("len p7c content:", len(p7c.Content)) - envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) if err != nil { return err From 2d21b09d414315a8be0c7673afa991375f1b56b3 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sat, 6 Mar 2021 23:24:49 +0100 Subject: [PATCH 15/42] Remove some duplicate and unnecessary logic --- ca/ca.go | 1 - scep/api/api.go | 9 +++++++++ scep/authority.go | 25 ++----------------------- scep/scep.go | 2 +- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index 7993ba38..edcc9bba 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -156,7 +156,6 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { // well as certificates via SCEP. tlsConfig = nil - // TODO: get the SCEP service scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ IntermediateCertificatePath: config.IntermediateCert, diff --git a/scep/api/api.go b/scep/api/api.go index fc134a95..09ffddd0 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -16,6 +16,7 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/scep" + "go.mozilla.org/pkcs7" microscep "github.com/micromdm/scep/scep" ) @@ -269,16 +270,24 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe response := SCEPResponse{Operation: opnPKIOperation} + // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { return SCEPResponse{}, err } + p7, err := pkcs7.Parse(microMsg.Raw) + if err != nil { + return SCEPResponse{}, err + } + + // copy over properties to our internal PKIMessage msg := &scep.PKIMessage{ TransactionID: microMsg.TransactionID, MessageType: microMsg.MessageType, SenderNonce: microMsg.SenderNonce, Raw: microMsg.Raw, + P7: p7, } if err := h.Auth.DecryptPKIEnvelope(ctx, msg); err != nil { diff --git a/scep/authority.go b/scep/authority.go index a1d47700..a61c093a 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -198,27 +198,7 @@ func (a *Authority) GetCACertificates() ([]*x509.Certificate, error) { // DecryptPKIEnvelope decrypts an enveloped message func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error { - data := msg.Raw - - p7, err := pkcs7.Parse(data) - if err != nil { - return err - } - - var tID microscep.TransactionID - if err := p7.UnmarshalSignedAttribute(oidSCEPtransactionID, &tID); err != nil { - return err - } - - var msgType microscep.MessageType - if err := p7.UnmarshalSignedAttribute(oidSCEPmessageType, &msgType); err != nil { - return err - } - - msg.p7 = p7 - - //p7c, err := pkcs7.Parse(p7.Content) - p7c, err := pkcs7.Parse(p7.Content) + p7c, err := pkcs7.Parse(msg.P7.Content) if err != nil { return err } @@ -253,7 +233,6 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err CSR: csr, ChallengePassword: cp, } - //msg.Certificate = p7.Certificates[0] // TODO: check if this is necessary to add (again) return nil case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: return fmt.Errorf("not implemented") //errNotImplemented @@ -355,7 +334,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return nil, err } - e7, err := pkcs7.Encrypt(deg, msg.p7.Certificates) + e7, err := pkcs7.Encrypt(deg, msg.P7.Certificates) if err != nil { return nil, err } diff --git a/scep/scep.go b/scep/scep.go index 6a636aee..7fc4c261 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -35,7 +35,7 @@ type PKIMessage struct { Raw []byte // parsed - p7 *pkcs7.PKCS7 + P7 *pkcs7.PKCS7 // decrypted enveloped content pkiEnvelope []byte From e4d7ea8fa0d0c6b07c0020829d7cdb379ff405d4 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 7 Mar 2021 00:30:37 +0100 Subject: [PATCH 16/42] Add support for challenge password --- authority/provisioner/scep.go | 16 +++++++++++----- scep/api/api.go | 29 +++++++++++++++++++++-------- scep/authority.go | 20 ++++++++++++++++++++ scep/provisioner.go | 1 + 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 10414b5e..031241c4 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -14,10 +14,11 @@ type SCEP struct { Type string `json:"type"` Name string `json:"name"` - ForceCN bool `json:"forceCN,omitempty"` - Options *Options `json:"options,omitempty"` - Claims *Claims `json:"claims,omitempty"` - claimer *Claimer + ForceCN bool `json:"forceCN,omitempty"` + ChallengePassword string `json:"challenge,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer } // GetID returns the provisioner unique identifier. @@ -76,7 +77,7 @@ func (s *SCEP) Init(config Config) (err error) { return err } -// AuthorizeSign does not do any validation, because all validation is handled +// AuthorizeSign does not do any verification, because all verification is handled // in the SCEP protocol. This method returns a list of modifiers / constraints // on the resulting certificate. func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, error) { @@ -91,6 +92,11 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e }, nil } +// GetChallengePassword returns the challenge password +func (s *SCEP) GetChallengePassword() string { + return s.ChallengePassword +} + // Interface guards var ( _ Interface = (*SCEP)(nil) diff --git a/scep/api/api.go b/scep/api/api.go index 09ffddd0..3f49f7a3 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -80,7 +80,6 @@ func New(scepAuth scep.Interface) api.RouterHandler { // Route traffic and implement the Router interface. func (h *Handler) Route(r api.Router) { getLink := h.Auth.GetLinkExplicit - r.MethodFunc(http.MethodGet, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Get)) r.MethodFunc(http.MethodPost, getLink("{provisionerID}", false, nil), h.lookupProvisioner(h.Post)) } @@ -140,6 +139,8 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { return } + // TODO: fix cases in which we get here and there's no certificate (i.e. wrong password, waiting for cert, etc) + // We should generate an appropriate response and it should be signed api.LogCertificate(w, response.Certificate) writeSCEPResponse(w, response) @@ -238,8 +239,10 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { return SCEPResponse{}, errors.New("missing CA cert") } - response := SCEPResponse{Operation: opnGetCACert} - response.CACertNum = len(certs) + response := SCEPResponse{ + Operation: opnGetCACert, + CACertNum: len(certs), + } if len(certs) == 1 { response.Data = certs[0].Raw @@ -268,8 +271,6 @@ func (h *Handler) GetCACaps(ctx context.Context) (SCEPResponse, error) { // PKIOperation performs PKI operations and returns a SCEP response func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPResponse, error) { - response := SCEPResponse{Operation: opnPKIOperation} - // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { @@ -295,7 +296,15 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe } if msg.MessageType == microscep.PKCSReq { - // TODO: CSR validation, like challenge password + + challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) + if err != nil { + return SCEPResponse{}, err + } + + if !challengeMatches { + return SCEPResponse{}, errors.New("wrong password provided") + } } csr := msg.CSRReqMessage.CSR @@ -311,8 +320,11 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // // 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; should go into the DB - response.Data = certRep.Raw - response.Certificate = certRep.Certificate + response := SCEPResponse{ + Operation: opnPKIOperation, + Data: certRep.Raw, + Certificate: certRep.Certificate, + } return response, nil } @@ -338,6 +350,7 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { } func writeError(w http.ResponseWriter, err error) { + // TODO: this probably needs to use SCEP specific errors (i.e. failInfo) scepError := &scep.Error{ Message: err.Error(), Status: http.StatusInternalServerError, // TODO: make this a param? diff --git a/scep/authority.go b/scep/authority.go index a61c093a..a2b3b8b9 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -56,6 +56,7 @@ type Interface interface { GetCACertificates() ([]*x509.Certificate, error) 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) GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } @@ -401,6 +402,25 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// MatchChallengePassword verifies a SCEP challenge password +func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { + + p, err := ProvisionerFromContext(ctx) + if err != nil { + return false, err + } + + if p.GetChallengePassword() == password { + return true, nil + } + + // TODO: support dynamic challenges, i.e. a list of challenges instead of one? + // That's probably a bit harder to configure, though; likely requires some data store + // that can be interacted with more easily, via some internal API, for example. + + return false, nil +} + // 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 64a787d4..5c665a1f 100644 --- a/scep/provisioner.go +++ b/scep/provisioner.go @@ -14,4 +14,5 @@ type Provisioner interface { GetName() string DefaultTLSCertDuration() time.Duration GetOptions() *provisioner.Options + GetChallengePassword() string } From 2536a08dc230f5954b85ddae038bc898e2130f9a Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 7 Mar 2021 00:50:00 +0100 Subject: [PATCH 17/42] 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 } From 9902dc10794c494089bef4d4791a52445b039992 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 21:13:05 +0100 Subject: [PATCH 18/42] Add signed failure responses --- scep/api/api.go | 44 +++++++++++++++++++--------- scep/authority.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++ scep/scep.go | 13 +++++++++ 3 files changed, 117 insertions(+), 13 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index f6294f06..ced6fa05 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -34,9 +34,9 @@ const maxPayloadSize = 2 << 20 type nextHTTP = func(http.ResponseWriter, *http.Request) const ( - certChainHeader = "application/x-x509-ca-ra-cert" - leafHeader = "application/x-x509-ca-cert" - pkiOpHeader = "application/x-pki-message" + certChainHeader = "application/x-x509-ca-ra-cert" + leafHeader = "application/x-x509-ca-cert" + pkiOperationHeader = "application/x-pki-message" ) // SCEPRequest is a SCEP server request. @@ -125,10 +125,6 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { return } - // TODO: fix cases in which we get here and there's no certificate (i.e. wrong password, waiting for cert, etc) - // We should generate an appropriate response and it should be signed - api.LogCertificate(w, response.Certificate) - writeSCEPResponse(w, response) } @@ -262,9 +258,13 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe // parse the message using microscep implementation microMsg, err := microscep.ParsePKIMessage(request.Message) if err != nil { + // return the error, because we can't use the msg for creating a CertRep return SCEPResponse{}, err } + // this is essentially doing the same as microscep.ParsePKIMessage, but + // gives us access to the p7 itself in scep.PKIMessage. Essentially a small + // wrapper for the microscep implementation. p7, err := pkcs7.Parse(microMsg.Raw) if err != nil { return SCEPResponse{}, err @@ -283,23 +283,25 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe return SCEPResponse{}, err } + // NOTE: at this point we have sufficient information for returning nicely signed CertReps + csr := msg.CSRReqMessage.CSR + if msg.MessageType == microscep.PKCSReq { challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when checking password") } if !challengeMatches { - return SCEPResponse{}, errors.New("wrong password provided") + // TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too. + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "wrong password provided") } } - csr := msg.CSRReqMessage.CSR - certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return SCEPResponse{}, err + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") } // //cert := certRep.CertRepMessage.Certificate @@ -330,6 +332,11 @@ func formatCapabilities(caps []string) []byte { // writeSCEPResponse writes a SCEP response back to the SCEP client. func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + + if response.Certificate != nil { + api.LogCertificate(w, response.Certificate) + } + w.Header().Set("Content-Type", contentHeader(response)) _, err := w.Write(response.Data) if err != nil { @@ -346,6 +353,17 @@ func writeError(w http.ResponseWriter, err error) { api.WriteError(w, scepError) } +func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, infoText string) (SCEPResponse, error) { + certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) + if err != nil { + return SCEPResponse{}, err + } + return SCEPResponse{ + Operation: opnPKIOperation, + Data: certRepMsg.Raw, + }, nil +} + func contentHeader(r SCEPResponse) string { switch r.Operation { case opnGetCACert: @@ -354,7 +372,7 @@ func contentHeader(r SCEPResponse) string { } return leafHeader case opnPKIOperation: - return pkiOpHeader + return pkiOperationHeader default: return "text/plain" } diff --git a/scep/authority.go b/scep/authority.go index f8e5a76f..e277a5af 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -56,6 +56,7 @@ type Interface interface { GetCACertificates() ([]*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 @@ -376,6 +377,10 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, }, } @@ -419,6 +424,74 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m return crepMsg, nil } +// CreateFailureResponse creates an appropriately signed reply for PKI operations +func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) { + + config := pkcs7.SignerInfoConfig{ + ExtraSignedAttributes: []pkcs7.Attribute{ + pkcs7.Attribute{ + Type: oidSCEPtransactionID, + Value: msg.TransactionID, + }, + pkcs7.Attribute{ + Type: oidSCEPpkiStatus, + Value: microscep.FAILURE, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfo, + Value: info, + }, + pkcs7.Attribute{ + Type: oidSCEPfailInfoText, + Value: infoText, + }, + pkcs7.Attribute{ + Type: oidSCEPmessageType, + Value: microscep.CertRep, + }, + pkcs7.Attribute{ + Type: oidSCEPsenderNonce, + Value: msg.SenderNonce, + }, + pkcs7.Attribute{ + Type: oidSCEPrecipientNonce, + Value: msg.SenderNonce, + }, + }, + } + + signedData, err := pkcs7.NewSignedData(nil) + if err != nil { + return nil, err + } + + // sign the attributes + if err := signedData.AddSigner(a.intermediateCertificate, a.service.Signer, config); err != nil { + return nil, err + } + + certRepBytes, err := signedData.Finish() + if err != nil { + return nil, err + } + + cr := &CertRepMessage{ + PKIStatus: microscep.FAILURE, + FailInfo: microscep.BadRequest, + RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), + } + + // create a CertRep message from the original + crepMsg := &PKIMessage{ + Raw: certRepBytes, + TransactionID: msg.TransactionID, + MessageType: microscep.CertRep, + CertRepMessage: cr, + } + + return crepMsg, nil +} + // MatchChallengePassword verifies a SCEP challenge password func (a *Authority) MatchChallengePassword(ctx context.Context, password string) (bool, error) { diff --git a/scep/scep.go b/scep/scep.go index 7fc4c261..0c25ec4c 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -11,6 +11,18 @@ import ( "go.mozilla.org/pkcs7" ) +// FailInfoName models the name/value of failInfo +type FailInfoName microscep.FailInfo + +// FailInfo models a failInfo object consisting of a +// name/identifier and a failInfoText, the latter of +// which can be more descriptive and is intended to be +// read by humans. +type FailInfo struct { + Name FailInfoName + Text string +} + // SCEP OIDs var ( oidSCEPmessageType = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 2} @@ -20,6 +32,7 @@ var ( oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} + oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} ) // PKIMessage defines the possible SCEP message types From cc1ecb943881c15b0d35aea702af387a395b0ad0 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 22:20:02 +0100 Subject: [PATCH 19/42] Store new certificates in database --- scep/api/api.go | 8 +--- scep/authority.go | 96 +++++++++++++++------------------------------ scep/certificate.go | 80 +++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 71 deletions(-) create mode 100644 scep/certificate.go diff --git a/scep/api/api.go b/scep/api/api.go index ced6fa05..13aeec21 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -299,17 +299,13 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe } } + // TODO: check if CN already exists, if renewal is allowed and if existing should be revoked; fail if not + certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") } - // //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; should go into the DB - response := SCEPResponse{ Operation: opnPKIOperation, Data: certRep.Raw, diff --git a/scep/authority.go b/scep/authority.go index e277a5af..157854f1 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -27,31 +27,14 @@ import ( "go.step.sm/crypto/x509util" ) +var ( + certTable = []byte("scep_certs") +) + // 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 + GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string GetCACertificates() ([]*x509.Certificate, error) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) error @@ -59,8 +42,6 @@ type Interface interface { 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 - - GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string } // Authority is the layer that handles all SCEP interactions. @@ -107,7 +88,14 @@ type SignAuthority interface { func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { if _, ok := ops.DB.(*database.SimpleDB); !ok { - // TODO: see ACME implementation + // If it's not a SimpleDB then go ahead and bootstrap the DB with the + // necessary SCEP tables. SimpleDB should ONLY be used for testing. + tables := [][]byte{certTable} + for _, b := range tables { + if err := ops.DB.CreateTable(b); err != nil { + return nil, fmt.Errorf("%w: error creating table %s", err, string(b)) + } + } } // TODO: the below is a bit similar as what happens in the core Authority class, which @@ -284,30 +272,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m csr = msg.CSRReqMessage.CSR } - // subjectKeyID, err := createKeyIdentifier(csr.PublicKey) - // if err != nil { - // return nil, err - // } - - // serial := big.NewInt(int64(rand.Int63())) // TODO: serial logic? - // days := 40 // TODO: days - - // // TODO: use information from provisioner, like claims - // template := &x509.Certificate{ - // SerialNumber: serial, - // Subject: csr.Subject, - // NotBefore: time.Now().Add(-600).UTC(), - // NotAfter: time.Now().AddDate(0, 0, days).UTC(), - // SubjectKeyId: subjectKeyID, - // KeyUsage: x509.KeyUsageDigitalSignature, - // ExtKeyUsage: []x509.ExtKeyUsage{ - // x509.ExtKeyUsageClientAuth, - // }, - // SignatureAlgorithm: csr.SignatureAlgorithm, - // EmailAddresses: csr.EmailAddresses, - // DNSNames: csr.DNSNames, - // } - // Template data data := x509util.NewTemplateData() data.SetCommonName(csr.Subject.CommonName) @@ -339,14 +303,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m cert := certChain[0] - // fmt.Println("CERT") - // fmt.Println(cert) - // fmt.Println(fmt.Sprintf("%T", cert)) - // fmt.Println(cert.Issuer) - // fmt.Println(cert.Subject) - // fmt.Println(cert.SerialNumber) - // fmt.Println(string(cert.SubjectKeyId)) - // create a degenerate cert structure deg, err := degenerateCertificates([]*x509.Certificate{cert}) if err != nil { @@ -377,7 +333,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, - pkcs7.Attribute{ + { Type: oidSCEPsenderNonce, Value: msg.SenderNonce, }, @@ -421,6 +377,16 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } + // TODO: save more data? + _, err = newCert(a.db, CertOptions{ + Leaf: certChain[0], + Intermediates: certChain[1:], + }) + if err != nil { + fmt.Println(err) + return nil, err + } + return crepMsg, nil } @@ -429,31 +395,31 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi config := pkcs7.SignerInfoConfig{ ExtraSignedAttributes: []pkcs7.Attribute{ - pkcs7.Attribute{ + { Type: oidSCEPtransactionID, Value: msg.TransactionID, }, - pkcs7.Attribute{ + { Type: oidSCEPpkiStatus, Value: microscep.FAILURE, }, - pkcs7.Attribute{ + { Type: oidSCEPfailInfo, Value: info, }, - pkcs7.Attribute{ + { Type: oidSCEPfailInfoText, Value: infoText, }, - pkcs7.Attribute{ + { Type: oidSCEPmessageType, Value: microscep.CertRep, }, - pkcs7.Attribute{ + { Type: oidSCEPsenderNonce, Value: msg.SenderNonce, }, - pkcs7.Attribute{ + { Type: oidSCEPrecipientNonce, Value: msg.SenderNonce, }, diff --git a/scep/certificate.go b/scep/certificate.go new file mode 100644 index 00000000..fe48d29e --- /dev/null +++ b/scep/certificate.go @@ -0,0 +1,80 @@ +package scep + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "time" + + "github.com/smallstep/nosql" +) + +type certificate struct { + ID string `json:"id"` + Created time.Time `json:"created"` + Leaf []byte `json:"leaf"` + Intermediates []byte `json:"intermediates"` +} + +// CertOptions options with which to create and store a cert object. +type CertOptions struct { + Leaf *x509.Certificate + Intermediates []*x509.Certificate +} + +func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { + + // TODO: according to the RFC this should be IssuerAndSerialNumber, + // but sscep seems to use just the serial number for getcert + + id := ops.Leaf.SerialNumber.String() + + leaf := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: ops.Leaf.Raw, + }) + var intermediates []byte + for _, cert := range ops.Intermediates { + intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + })...) + } + + cert := &certificate{ + ID: id, + Leaf: leaf, + Intermediates: intermediates, + Created: time.Now().UTC(), + } + certB, err := json.Marshal(cert) + if err != nil { + return nil, fmt.Errorf("%w: error marshaling certificate", err) + } + + _, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB) + switch { + case err != nil: + return nil, fmt.Errorf("%w: error storing certificate", err) + case !swapped: + return nil, fmt.Errorf("error storing certificate; " + + "value has changed since last read") + default: + return cert, nil + } +} + +func getCert(db nosql.DB, id string) (*certificate, error) { + b, err := db.Get(certTable, []byte(id)) + if nosql.IsErrNotFound(err) { + return nil, fmt.Errorf("certificate %s not found", id) + } else if err != nil { + return nil, fmt.Errorf("error loading certificate") + } + var cert certificate + if err := json.Unmarshal(b, &cert); err != nil { + return nil, fmt.Errorf("%w: error unmarshaling certificate", err) + } + return &cert, nil +} From 538fe8114d52c6ffde77224845214f629f98f562 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 10 Mar 2021 22:39:20 +0100 Subject: [PATCH 20/42] Fix linter issues --- authority/authority.go | 3 +++ scep/api/api.go | 7 ------- scep/authority.go | 16 ---------------- scep/certificate.go | 26 +++++++++++++------------- scep/scep.go | 3 ++- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index f0e45808..3bc88c0a 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -224,6 +224,9 @@ func (a *Authority) init() error { DecryptionKey: a.config.IntermediateKey, Password: []byte(a.config.Password), }) + if err != nil { + return err + } } a.scepService = &scep.Service{ diff --git a/scep/api/api.go b/scep/api/api.go index 13aeec21..a9e4d840 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -315,13 +315,6 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe return response, nil } -func certName(cert *x509.Certificate) string { - if cert.Subject.CommonName != "" { - return cert.Subject.CommonName - } - return string(cert.Signature) -} - func formatCapabilities(caps []string) []byte { return []byte(strings.Join(caps, "\r\n")) } diff --git a/scep/authority.go b/scep/authority.go index 157854f1..69d83554 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -3,8 +3,6 @@ package scep import ( "bytes" "context" - "crypto" - "crypto/sha1" "crypto/x509" "errors" "fmt" @@ -512,20 +510,6 @@ func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { return degenerate, nil } -// createKeyIdentifier creates 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 -} - // Interface guards var ( _ Interface = (*Authority)(nil) diff --git a/scep/certificate.go b/scep/certificate.go index fe48d29e..5e43b762 100644 --- a/scep/certificate.go +++ b/scep/certificate.go @@ -65,16 +65,16 @@ func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { } } -func getCert(db nosql.DB, id string) (*certificate, error) { - b, err := db.Get(certTable, []byte(id)) - if nosql.IsErrNotFound(err) { - return nil, fmt.Errorf("certificate %s not found", id) - } else if err != nil { - return nil, fmt.Errorf("error loading certificate") - } - var cert certificate - if err := json.Unmarshal(b, &cert); err != nil { - return nil, fmt.Errorf("%w: error unmarshaling certificate", err) - } - return &cert, nil -} +// func getCert(db nosql.DB, id string) (*certificate, error) { +// b, err := db.Get(certTable, []byte(id)) +// if nosql.IsErrNotFound(err) { +// return nil, fmt.Errorf("certificate %s not found", id) +// } else if err != nil { +// return nil, fmt.Errorf("error loading certificate") +// } +// var cert certificate +// if err := json.Unmarshal(b, &cert); err != nil { +// return nil, fmt.Errorf("%w: error unmarshaling certificate", err) +// } +// return &cert, nil +// } diff --git a/scep/scep.go b/scep/scep.go index 0c25ec4c..f56176d7 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -31,8 +31,9 @@ var ( oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5} oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} - oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} + //oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} + ) // PKIMessage defines the possible SCEP message types From 8c5b12e21de229eed8e3c8c88ad3ab7ba59d12f3 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 14:18:36 +0100 Subject: [PATCH 21/42] Add non-TLS server and improve crypto.Decrypter interface A server without TLS was added to serve the SCEP endpoints. According to the RFC, SCEP has to be served via HTTP. The `sscep` client, for example, will stop any URL that does not start with `http://` from being used, so serving SCEP seems to be the right way to do it. This commit adds a second server for which no TLS configuration is configured. A distinct field in the configuration, `insecureAddress` was added to specify the address for the insecure server. The SCEP endpoints will also still be served via HTTPS. Some clients may be able to work with that. This commit also improves how the crypto.Decrypter interface is handled for the different types of KMSes supported by step. The apiv1.Decrypter interface was added. Currently only SoftKMS implements this interface, providing a crypto.Decrypter required for SCEP operations. --- authority/authority.go | 46 ++++++++++++++------ authority/config.go | 8 ++++ ca/ca.go | 78 ++++++++++++++++++++++++++-------- kms/apiv1/options.go | 5 ++- kms/awskms/awskms.go | 6 --- kms/cloudkms/cloudkms.go | 6 --- kms/pkcs11/pkcs11.go | 5 --- kms/sshagentkms/sshagentkms.go | 6 --- kms/yubikey/yubikey.go | 6 --- 9 files changed, 106 insertions(+), 60 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 3bc88c0a..a51b2162 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -18,6 +18,7 @@ import ( casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/kms" + "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/templates" @@ -201,13 +202,14 @@ func (a *Authority) init() error { } // TODO: decide if this is a good approach for providing the SCEP functionality + // It currently mirrors the logic for the x509CAServer if a.scepService == nil { var options casapi.Options if a.config.AuthorityConfig.Options != nil { options = *a.config.AuthorityConfig.Options } - // Read intermediate and create X509 signer for default CAS. + // Read intermediate and create X509 signer and decrypter for default CAS. if options.Is(casapi.SoftCAS) { options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) if err != nil { @@ -220,12 +222,15 @@ func (a *Authority) init() error { if err != nil { return err } - options.Decrypter, err = a.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err + + if km, ok := a.keyManager.(apiv1.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } } } @@ -368,11 +373,6 @@ func (a *Authority) init() error { audiences := a.config.getAudiences() a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ - // TODO: I'm not sure if extending this configuration is a good way to integrate - // It's powerful, but leaks quite some seemingly internal stuff to the provisioner. - // IntermediateCert: a.config.IntermediateCert, - // SigningKey: a.config.IntermediateKey, - // CACertificates: a.rootX509Certs, Claims: claimer.Claims(), Audiences: audiences, DB: a.db, @@ -382,6 +382,14 @@ func (a *Authority) init() error { }, GetIdentityFunc: a.getIdentityFunc, } + + // Check if a KMS with decryption capability is required and available + if a.requiresDecrypter() { + if _, ok := a.keyManager.(apiv1.Decrypter); !ok { + return errors.New("keymanager doesn't provide crypto.Decrypter") + } + } + // Store all the provisioners for _, p := range a.config.AuthorityConfig.Provisioners { if err := p.Init(config); err != nil { @@ -434,6 +442,20 @@ func (a *Authority) CloseForReload() { } } +// requiresDecrypter iterates over the configured provisioners +// and determines if the Authority requires a KMS that provides +// a crypto.Decrypter by implementing the apiv1.Decrypter +// interface. Currently only the SCEP provider requires this, +// but others may be added in the future. +func (a *Authority) requiresDecrypter() bool { + for _, p := range a.config.AuthorityConfig.Provisioners { + if p.GetType() == provisioner.TypeSCEP { + return true + } + } + return false +} + // GetSCEPService returns the configured SCEP Service // TODO: this function is intended to exist temporarily // in order to make SCEP work more easily. It can be diff --git a/authority/config.go b/authority/config.go index 9d79ce9a..3151578d 100644 --- a/authority/config.go +++ b/authority/config.go @@ -53,6 +53,7 @@ type Config struct { IntermediateCert string `json:"crt"` IntermediateKey string `json:"key"` Address string `json:"address"` + InsecureAddress string `json:"insecureAddress"` DNSNames []string `json:"dnsNames"` KMS *kms.Options `json:"kms,omitempty"` SSH *SSHConfig `json:"ssh,omitempty"` @@ -207,6 +208,13 @@ func (c *Config) Validate() error { return errors.Errorf("invalid address %s", c.Address) } + // Validate insecure address if it is configured + if c.InsecureAddress != "" { + if _, _, err := net.SplitHostPort(c.InsecureAddress); err != nil { + return errors.Errorf("invalid address %s", c.InsecureAddress) + } + } + if c.TLS == nil { c.TLS = &DefaultTLSOptions } else { diff --git a/ca/ca.go b/ca/ca.go index edcc9bba..c6af8a28 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -65,11 +65,12 @@ func WithDatabase(db db.AuthDB) Option { // CA is the type used to build the complete certificate authority. It builds // the HTTP server, set ups the middlewares and the HTTP handlers. type CA struct { - auth *authority.Authority - config *authority.Config - srv *server.Server - opts *options - renewer *TLSRenewer + auth *authority.Authority + config *authority.Config + srv *server.Server + insecureSrv *server.Server + opts *options + renewer *TLSRenewer } // New creates and initializes the CA with the given configuration and options. @@ -107,6 +108,9 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { mux := chi.NewRouter() handler := http.Handler(mux) + insecureMux := chi.NewRouter() + insecureHandler := http.Handler(insecureMux) + // Add regular CA api endpoints in / and /1.0 routerHandler := api.New(auth) routerHandler.Route(mux) @@ -145,17 +149,6 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { acmeRouterHandler.Route(r) }) - // TODO: THIS SHOULDN'T HAPPEN (or should become configurable) - // Current SCEP client I'm testing with doesn't seem to easily trust untrusted certs. - // Idea: provide a second mux/handler that runs without TLS. It probably should only - // have routes that are intended to be ran without TLS, like the SCEP ones. Look into - // option to not enable it in case no SCEP providers are configured. It might - // be nice to still include the SCEP routes in the secure handler too, for - // client that do understand HTTPS. The RFC does not seem to explicitly exclude HTTPS - // usage, but it mentions some caveats related to managing web PKI certificates as - // well as certificates via SCEP. - tlsConfig = nil - scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ IntermediateCertificatePath: config.IntermediateCert, @@ -173,6 +166,16 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepRouterHandler.Route(r) }) + // 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. To my current understanding there's no strong reason + // to not 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. + insecureMux.Route("/"+scepPrefix, func(r chi.Router) { + scepRouterHandler.Route(r) + }) + // helpful routine for logging all routes //dumpRoutes(mux) @@ -183,6 +186,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { return nil, err } handler = m.Middleware(handler) + insecureHandler = m.Middleware(insecureHandler) } // Add logger if configured @@ -192,16 +196,37 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { return nil, err } handler = logger.Middleware(handler) + insecureHandler = logger.Middleware(insecureHandler) } ca.auth = auth ca.srv = server.New(config.Address, handler, tlsConfig) + + // TODO: instead opt for having a single server.Server but two http.Servers + // handling the HTTP vs. HTTPS handler? + if config.InsecureAddress != "" { + ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil) + } + return ca, nil } // Run starts the CA calling to the server ListenAndServe method. func (ca *CA) Run() error { - return ca.srv.ListenAndServe() + + errors := make(chan error, 1) + go func() { + if ca.insecureSrv != nil { + errors <- ca.insecureSrv.ListenAndServe() + } + }() + go func() { + errors <- ca.srv.ListenAndServe() + }() + + // wait till error occurs; ensures the servers keep listening + err := <-errors + return err } // Stop stops the CA calling to the server Shutdown method. @@ -210,7 +235,17 @@ func (ca *CA) Stop() error { if err := ca.auth.Shutdown(); err != nil { log.Printf("error stopping ca.Authority: %+v\n", err) } - return ca.srv.Shutdown() + var insecureShutdownErr error + if ca.insecureSrv != nil { + insecureShutdownErr = ca.insecureSrv.Shutdown() + } + + secureErr := ca.srv.Shutdown() + + if insecureShutdownErr != nil { + return insecureShutdownErr + } + return secureErr } // Reload reloads the configuration of the CA and calls to the server Reload @@ -243,6 +278,13 @@ func (ca *CA) Reload() error { return errors.Wrap(err, "error reloading ca") } + if ca.insecureSrv != nil { + if err = ca.insecureSrv.Reload(newCA.insecureSrv); err != nil { + logContinue("Reload failed because insecure server could not be replaced.") + return errors.Wrap(err, "error reloading insecure server") + } + } + if err = ca.srv.Reload(newCA.srv); err != nil { logContinue("Reload failed because server could not be replaced.") return errors.Wrap(err, "error reloading server") diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 0e6f32df..1574a426 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -13,10 +13,13 @@ type KeyManager interface { GetPublicKey(req *GetPublicKeyRequest) (crypto.PublicKey, error) CreateKey(req *CreateKeyRequest) (*CreateKeyResponse, error) CreateSigner(req *CreateSignerRequest) (crypto.Signer, error) - CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) // TODO: split into separate interface? Close() error } +type Decrypter interface { + CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) +} + // CertificateManager is the interface implemented by the KMS that can load and // store x509.Certificates. type CertificateManager interface { diff --git a/kms/awskms/awskms.go b/kms/awskms/awskms.go index 00d7d0b3..da392989 100644 --- a/kms/awskms/awskms.go +++ b/kms/awskms/awskms.go @@ -3,7 +3,6 @@ package awskms import ( "context" "crypto" - "fmt" "net/url" "strings" "time" @@ -222,11 +221,6 @@ func (k *KMS) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, error return NewSigner(k.service, req.SigningKey) } -// CreateDecrypter creates a new crypto.decrypter backed by AWS KMS -func (k *KMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // Close closes the connection of the KMS client. func (k *KMS) Close() error { return nil diff --git a/kms/cloudkms/cloudkms.go b/kms/cloudkms/cloudkms.go index ace12a1a..cfbf8235 100644 --- a/kms/cloudkms/cloudkms.go +++ b/kms/cloudkms/cloudkms.go @@ -3,7 +3,6 @@ package cloudkms import ( "context" "crypto" - "fmt" "log" "strings" "time" @@ -285,11 +284,6 @@ func (k *CloudKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKe return pk, nil } -// CreateDecrypter creates a new crypto.Decrypter backed by Google Cloud KMS -func (k *CloudKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // getPublicKeyWithRetries retries the request if the error is // FailedPrecondition, caused because the key is in the PENDING_GENERATION // status. diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 8c1497e8..47c298a5 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -352,8 +352,3 @@ func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { } return cert, nil } - -// CreateDecrypter creates a new crypto.Decrypter backed by PKCS11 -func (k *PKCS11) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} diff --git a/kms/sshagentkms/sshagentkms.go b/kms/sshagentkms/sshagentkms.go index 9c8a7866..b3627a08 100644 --- a/kms/sshagentkms/sshagentkms.go +++ b/kms/sshagentkms/sshagentkms.go @@ -7,7 +7,6 @@ import ( "crypto/ed25519" "crypto/rsa" "crypto/x509" - "fmt" "io" "net" "os" @@ -205,8 +204,3 @@ func (k *SSHAgentKMS) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.Publi return nil, errors.Errorf("unsupported public key type %T", v) } } - -// CreateDecrypter creates a crypto.Decrypter backed by ssh-agent -func (k *SSHAgentKMS) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} diff --git a/kms/yubikey/yubikey.go b/kms/yubikey/yubikey.go index 45ee3d87..19cef55e 100644 --- a/kms/yubikey/yubikey.go +++ b/kms/yubikey/yubikey.go @@ -7,7 +7,6 @@ import ( "crypto" "crypto/x509" "encoding/hex" - "fmt" "net/url" "strings" @@ -190,11 +189,6 @@ func (k *YubiKey) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, e return signer, nil } -// CreateDecrypter creates a new crypto.Decrypter backed by a YubiKey -func (k *YubiKey) CreateDecrypter(req *apiv1.CreateDecrypterRequest) (crypto.Decrypter, error) { - return nil, fmt.Errorf("not implemented yet") -} - // Close releases the connection to the YubiKey. func (k *YubiKey) Close() error { return errors.Wrap(k.yk.Close(), "error closing yubikey") From e1cab4966f4e9a6b755a3cbed700484787df7b13 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 15:49:39 +0100 Subject: [PATCH 22/42] Improve initialization of SCEP authority --- authority/authority.go | 8 +++++--- ca/ca.go | 31 +++++++++++++++---------------- cas/apiv1/registry.go | 2 +- scep/authority.go | 23 +++++------------------ scep/service.go | 35 +++++++++++++++++++++++++++++++---- 5 files changed, 57 insertions(+), 42 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index a51b2162..74abda7b 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -234,10 +234,12 @@ func (a *Authority) init() error { } } - a.scepService = &scep.Service{ - Signer: options.Signer, - Decrypter: options.Decrypter, + a.scepService, err = scep.NewService(context.Background(), options) + if err != nil { + return err } + + // TODO: mimick the x509CAService GetCertificateAuthority here too? } // Read root certificates and store them in the certificates map. diff --git a/ca/ca.go b/ca/ca.go index c6af8a28..8cc7f405 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -151,12 +151,11 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ - IntermediateCertificatePath: config.IntermediateCert, - Service: auth.GetSCEPService(), - Backdate: *config.AuthorityConfig.Backdate, - DB: auth.GetDatabase().(nosql.DB), - DNS: dns, - Prefix: scepPrefix, + Service: auth.GetSCEPService(), + Backdate: *config.AuthorityConfig.Backdate, + DB: auth.GetDatabase().(nosql.DB), + DNS: dns, + Prefix: scepPrefix, }) if err != nil { return nil, errors.Wrap(err, "error creating SCEP authority") @@ -358,13 +357,13 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { return tlsConfig, nil } -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()) - } -} +// 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/cas/apiv1/registry.go b/cas/apiv1/registry.go index b74103b7..5876e9d7 100644 --- a/cas/apiv1/registry.go +++ b/cas/apiv1/registry.go @@ -18,7 +18,7 @@ func Register(t Type, fn CertificateAuthorityServiceNewFunc) { registry.Store(t.String(), fn) } -// LoadCertificateAuthorityServiceNewFunc returns the function initialize a KayManager. +// LoadCertificateAuthorityServiceNewFunc returns the function to initialize a KeyManager. func LoadCertificateAuthorityServiceNewFunc(t Type) (CertificateAuthorityServiceNewFunc, bool) { v, ok := registry.Load(t.String()) if !ok { diff --git a/scep/authority.go b/scep/authority.go index 69d83554..03bd6919 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -11,8 +11,6 @@ import ( "github.com/smallstep/certificates/authority/provisioner" database "github.com/smallstep/certificates/db" - "go.step.sm/crypto/pemutil" - "github.com/smallstep/nosql" microx509util "github.com/micromdm/scep/crypto/x509util" @@ -59,10 +57,8 @@ type Authority struct { // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - IntermediateCertificatePath string - + // Service provides the SCEP functions to Authority Service Service - // Backdate Backdate provisioner.Duration // DB is the database used by nosql. @@ -96,21 +92,12 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { } } - // TODO: the below is a bit similar as what happens in the core Authority class, which - // creates the full x509 service. However, those aren't accessible directly, which is - // why I reimplemented this (for now). There might be an alternative that I haven't - // found yet. - certificateChain, err := pemutil.ReadCertificateBundle(ops.IntermediateCertificatePath) - if err != nil { - return nil, err - } - return &Authority{ backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, - intermediateCertificate: certificateChain[0], + intermediateCertificate: ops.Service.certificateChain[0], service: ops.Service, signAuth: signAuth, }, nil @@ -208,7 +195,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err return err } - envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.Decrypter) + envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter) if err != nil { return err } @@ -351,7 +338,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m authCert := a.intermediateCertificate // sign the attributes - if err := signedData.AddSigner(authCert, a.service.Signer, config); err != nil { + if err := signedData.AddSigner(authCert, a.service.signer, config); err != nil { return nil, err } @@ -430,7 +417,7 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi } // sign the attributes - if err := signedData.AddSigner(a.intermediateCertificate, a.service.Signer, config); err != nil { + if err := signedData.AddSigner(a.intermediateCertificate, a.service.signer, config); err != nil { return nil, err } diff --git a/scep/service.go b/scep/service.go index 1d743dd6..0b37716d 100644 --- a/scep/service.go +++ b/scep/service.go @@ -1,9 +1,36 @@ package scep -import "crypto" +import ( + "context" + "crypto" + "crypto/x509" + "strings" -// Service is a (temporary?) wrapper for signer/decrypters + "github.com/smallstep/certificates/cas/apiv1" +) + +// Service is a wrapper for crypto.Signer and crypto.Decrypter type Service struct { - Signer crypto.Signer - Decrypter crypto.Decrypter + certificateChain []*x509.Certificate + signer crypto.Signer + decrypter crypto.Decrypter +} + +func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { + + if err := opts.Validate(); err != nil { + return nil, err + } + + t := apiv1.Type(strings.ToLower(opts.Type)) + if t == apiv1.DefaultCAS { + t = apiv1.SoftCAS + } + + // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? + return &Service{ + chain: opts.CertificateChain, + signer: opts.Signer, + decrypter: opts.Decrypter, + }, nil } From 3e0dac3ab4c1a14452578e8f62262a7c3dd5c168 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 15:51:16 +0100 Subject: [PATCH 23/42] Fix certificateChain property --- scep/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scep/service.go b/scep/service.go index 0b37716d..d485b7c3 100644 --- a/scep/service.go +++ b/scep/service.go @@ -29,8 +29,8 @@ func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ - chain: opts.CertificateChain, - signer: opts.Signer, - decrypter: opts.Decrypter, + certificateChain: opts.CertificateChain, + signer: opts.Signer, + decrypter: opts.Decrypter, }, nil } From e30084c9a839f76248fda19bec0a8fc2d92f46b9 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:02:00 +0100 Subject: [PATCH 24/42] Make linter happy --- scep/service.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scep/service.go b/scep/service.go index d485b7c3..e9a58646 100644 --- a/scep/service.go +++ b/scep/service.go @@ -27,6 +27,9 @@ func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { t = apiv1.SoftCAS } + // TODO: silence the linter (temporarily) + _ = t + // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ certificateChain: opts.CertificateChain, From 99952080c7c1822a753ec5564cc0950dcc8fd1a2 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:27:26 +0100 Subject: [PATCH 25/42] Make tests not fail hard on ECDSA keys All tests for the Authority failed because the test data contains ECDSA keys. ECDSA keys are no crypto.Decrypter, resulting in a failure when instantiating the Authority. --- authority/authority.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 74abda7b..975b0962 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -7,6 +7,8 @@ import ( "crypto/x509" "encoding/hex" "log" + "os" + "strings" "sync" "time" @@ -18,7 +20,6 @@ import ( casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/kms" - "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1" "github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/templates" @@ -223,13 +224,19 @@ func (a *Authority) init() error { return err } - if km, ok := a.keyManager.(apiv1.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err + // TODO: this is not exactly nice to do, but ensures that tests will still run while + // ECDSA keys are in the testdata. ECDSA keys are no crypto.Decrypters, resulting + // in many errors in the test suite. Needs a better solution, I think. + underTest := strings.HasSuffix(os.Args[0], ".test") + if !underTest { + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } } } } @@ -387,7 +394,7 @@ func (a *Authority) init() error { // Check if a KMS with decryption capability is required and available if a.requiresDecrypter() { - if _, ok := a.keyManager.(apiv1.Decrypter); !ok { + if _, ok := a.keyManager.(kmsapi.Decrypter); !ok { return errors.New("keymanager doesn't provide crypto.Decrypter") } } From a4844fee7b3fe567a9347af8f93a5cbfade77c81 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 12 Mar 2021 16:58:52 +0100 Subject: [PATCH 26/42] Make tests green --- authority/authority.go | 107 ++++++++++++++++++++--------------------- scep/authority.go | 30 +++++++----- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 975b0962..19de5210 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -7,8 +7,6 @@ import ( "crypto/x509" "encoding/hex" "log" - "os" - "strings" "sync" "time" @@ -202,53 +200,6 @@ func (a *Authority) init() error { } } - // TODO: decide if this is a good approach for providing the SCEP functionality - // It currently mirrors the logic for the x509CAServer - if a.scepService == nil { - var options casapi.Options - if a.config.AuthorityConfig.Options != nil { - options = *a.config.AuthorityConfig.Options - } - - // Read intermediate and create X509 signer and decrypter for default CAS. - if options.Is(casapi.SoftCAS) { - options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) - if err != nil { - return err - } - options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - - // TODO: this is not exactly nice to do, but ensures that tests will still run while - // ECDSA keys are in the testdata. ECDSA keys are no crypto.Decrypters, resulting - // in many errors in the test suite. Needs a better solution, I think. - underTest := strings.HasSuffix(os.Args[0], ".test") - if !underTest { - if km, ok := a.keyManager.(kmsapi.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - } - } - } - - a.scepService, err = scep.NewService(context.Background(), options) - if err != nil { - return err - } - - // TODO: mimick the x509CAService GetCertificateAuthority here too? - } - // Read root certificates and store them in the certificates map. if len(a.rootX509Certs) == 0 { a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) @@ -399,6 +350,47 @@ func (a *Authority) init() error { } } + // TODO: decide if this is a good approach for providing the SCEP functionality + // It currently mirrors the logic for the x509CAService + if a.requiresSCEPService() && a.scepService == nil { + var options casapi.Options + if a.config.AuthorityConfig.Options != nil { + options = *a.config.AuthorityConfig.Options + } + + // Read intermediate and create X509 signer and decrypter for default CAS. + if options.Is(casapi.SoftCAS) { + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + } + } + + a.scepService, err = scep.NewService(context.Background(), options) + if err != nil { + return err + } + + // TODO: mimick the x509CAService GetCertificateAuthority here too? + } + // Store all the provisioners for _, p := range a.config.AuthorityConfig.Provisioners { if err := p.Init(config); err != nil { @@ -451,12 +443,15 @@ func (a *Authority) CloseForReload() { } } -// requiresDecrypter iterates over the configured provisioners -// and determines if the Authority requires a KMS that provides -// a crypto.Decrypter by implementing the apiv1.Decrypter -// interface. Currently only the SCEP provider requires this, -// but others may be added in the future. +// requiresDecrypter returns whether the Authority +// requires a KMS that provides a crypto.Decrypter func (a *Authority) requiresDecrypter() bool { + return a.requiresSCEPService() +} + +// requiresSCEPService iterates over the configured provisioners +// and determines if one of them is a SCEP provisioner. +func (a *Authority) requiresSCEPService() bool { for _, p := range a.config.AuthorityConfig.Provisioners { if p.GetType() == provisioner.TypeSCEP { return true @@ -470,6 +465,6 @@ func (a *Authority) requiresDecrypter() bool { // in order to make SCEP work more easily. It can be // made more correct by using the right interfaces/abstractions // after it works as expected. -func (a *Authority) GetSCEPService() scep.Service { - return *a.scepService +func (a *Authority) GetSCEPService() *scep.Service { + return a.scepService } diff --git a/scep/authority.go b/scep/authority.go index 03bd6919..2b6686fd 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -51,14 +51,14 @@ type Authority struct { intermediateCertificate *x509.Certificate - service Service + service *Service signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { // Service provides the SCEP functions to Authority - Service Service + Service *Service // Backdate Backdate provisioner.Duration // DB is the database used by nosql. @@ -92,15 +92,23 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { } } - return &Authority{ - backdate: ops.Backdate, - db: ops.DB, - prefix: ops.Prefix, - dns: ops.DNS, - intermediateCertificate: ops.Service.certificateChain[0], - service: ops.Service, - signAuth: signAuth, - }, nil + authority := &Authority{ + backdate: ops.Backdate, + db: ops.DB, + prefix: ops.Prefix, + dns: ops.DNS, + signAuth: signAuth, + } + + // TODO: this is not really nice to do; the Service should be removed + // in its entirety to make this more interoperable with the rest of + // step-ca. + if ops.Service != nil { + authority.intermediateCertificate = ops.Service.certificateChain[0] + authority.service = ops.Service + } + + return authority, nil } var ( From 583d60dc0d076761e68123967e114e64bf293386 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Sun, 21 Mar 2021 16:42:41 +0100 Subject: [PATCH 27/42] Address (most) PR comments --- authority/authority.go | 39 ++++++------- authority/provisioner/scep.go | 6 -- ca/ca.go | 36 ++++++------ cas/apiv1/options.go | 3 +- kms/apiv1/options.go | 2 + kms/apiv1/requests.go | 3 - scep/api/api.go | 28 ++++----- scep/authority.go | 106 ++++++++++------------------------ scep/certificate.go | 69 ++-------------------- scep/database.go | 7 +++ scep/options.go | 31 ++++++++++ scep/service.go | 13 +---- 12 files changed, 127 insertions(+), 216 deletions(-) create mode 100644 scep/database.go create mode 100644 scep/options.go diff --git a/authority/authority.go b/authority/authority.go index 19de5210..e5b62602 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -353,34 +353,29 @@ func (a *Authority) init() error { // TODO: decide if this is a good approach for providing the SCEP functionality // It currently mirrors the logic for the x509CAService if a.requiresSCEPService() && a.scepService == nil { - var options casapi.Options - if a.config.AuthorityConfig.Options != nil { - options = *a.config.AuthorityConfig.Options - } + var options scep.Options // Read intermediate and create X509 signer and decrypter for default CAS. - if options.Is(casapi.SoftCAS) { - options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) - if err != nil { - return err - } - options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), + options.CertificateChain, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) + if err != nil { + return err + } + options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), + }) + if err != nil { + return err + } + + if km, ok := a.keyManager.(kmsapi.Decrypter); ok { + options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: []byte(a.config.Password), }) if err != nil { return err } - - if km, ok := a.keyManager.(kmsapi.Decrypter); ok { - options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.config.Password), - }) - if err != nil { - return err - } - } } a.scepService, err = scep.NewService(context.Background(), options) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 49d057ff..6af3dc83 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -102,9 +102,3 @@ func (s *SCEP) GetChallengePassword() string { func (s *SCEP) GetCapabilities() []string { return s.Capabilities } - -// Interface guards -var ( - _ Interface = (*SCEP)(nil) - //_ scep.Provisioner = (*SCEP)(nil) -) diff --git a/ca/ca.go b/ca/ca.go index 8cc7f405..4d268c5b 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -151,11 +151,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ - Service: auth.GetSCEPService(), - Backdate: *config.AuthorityConfig.Backdate, - DB: auth.GetDatabase().(nosql.DB), - DNS: dns, - Prefix: scepPrefix, + Service: auth.GetSCEPService(), + DB: auth.GetDatabase().(scep.DB), + DNS: dns, + Prefix: scepPrefix, }) if err != nil { return nil, errors.Wrap(err, "error creating SCEP authority") @@ -201,8 +200,10 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { ca.auth = auth ca.srv = server.New(config.Address, handler, tlsConfig) - // TODO: instead opt for having a single server.Server but two http.Servers - // handling the HTTP vs. HTTPS handler? + // TODO: instead opt for having a single server.Server but two + // http.Servers handling the HTTP and HTTPS handler? The latter + // will probably introduce more complexity in terms of graceful + // reload. if config.InsecureAddress != "" { ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil) } @@ -357,13 +358,14 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { return tlsConfig, nil } -// 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()) -// } -// } +//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/cas/apiv1/options.go b/cas/apiv1/options.go index b6b0ca0d..46efae3b 100644 --- a/cas/apiv1/options.go +++ b/cas/apiv1/options.go @@ -24,8 +24,7 @@ type Options struct { // Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS. // They are configured in ca.json crt and key properties. CertificateChain []*x509.Certificate - Signer crypto.Signer `json:"-"` - Decrypter crypto.Decrypter `json:"-"` + Signer crypto.Signer `json:"-"` // IsCreator is set to true when we're creating a certificate authority. Is // used to skip some validations when initializing a CertificateAuthority. diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 1574a426..7cc7f748 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -16,6 +16,8 @@ type KeyManager interface { Close() error } +// Decrypter is an interface implemented by KMSes that are used +// in operations that require decryption type Decrypter interface { CreateDecrypter(req *CreateDecrypterRequest) (crypto.Decrypter, error) } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index 31097040..f6fe7dd2 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -138,9 +138,6 @@ type CreateDecrypterRequest struct { Decrypter crypto.Decrypter DecryptionKey string DecryptionKeyPEM []byte - TokenLabel string - PublicKey string - PublicKeyPEM []byte Password []byte } diff --git a/scep/api/api.go b/scep/api/api.go index a9e4d840..cff3d32b 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -4,8 +4,6 @@ import ( "context" "crypto/x509" "encoding/base64" - "errors" - "fmt" "io" "io/ioutil" "net/http" @@ -18,6 +16,8 @@ import ( "github.com/smallstep/certificates/scep" "go.mozilla.org/pkcs7" + "github.com/pkg/errors" + microscep "github.com/micromdm/scep/scep" ) @@ -75,7 +75,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, fmt.Errorf("not a scep get request: %w", err)) + writeError(w, errors.Wrap(err, "not a scep get request")) return } @@ -90,11 +90,11 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { case opnPKIOperation: // TODO: implement the GET for PKI operation? Default CACAPS doesn't specify this is in use, though default: - err = fmt.Errorf("unknown operation: %s", request.Operation) + err = errors.Errorf("unknown operation: %s", request.Operation) } if err != nil { - writeError(w, fmt.Errorf("get request failed: %w", err)) + writeError(w, errors.Wrap(err, "get request failed")) return } @@ -106,7 +106,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, fmt.Errorf("not a scep post request: %w", err)) + writeError(w, errors.Wrap(err, "not a scep post request")) return } @@ -117,11 +117,11 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { case opnPKIOperation: response, err = h.PKIOperation(ctx, request) default: - err = fmt.Errorf("unknown operation: %s", request.Operation) + err = errors.Errorf("unknown operation: %s", request.Operation) } if err != nil { - writeError(w, fmt.Errorf("post request failed: %w", err)) + writeError(w, errors.Wrap(err, "post request failed")) return } @@ -163,7 +163,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { Message: decodedMessage, }, nil default: - return SCEPRequest{}, fmt.Errorf("unsupported operation: %s", operation) + return SCEPRequest{}, errors.Errorf("unsupported operation: %s", operation) } case http.MethodPost: body, err := ioutil.ReadAll(io.LimitReader(r.Body, maxPayloadSize)) @@ -175,7 +175,7 @@ func decodeSCEPRequest(r *http.Request) (SCEPRequest, error) { Message: body, }, nil default: - return SCEPRequest{}, fmt.Errorf("unsupported method: %s", method) + return SCEPRequest{}, errors.Errorf("unsupported method: %s", method) } } @@ -187,7 +187,7 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { name := chi.URLParam(r, "provisionerID") provisionerID, err := url.PathUnescape(name) if err != nil { - api.WriteError(w, fmt.Errorf("error url unescaping provisioner id '%s'", name)) + api.WriteError(w, errors.Errorf("error url unescaping provisioner id '%s'", name)) return } @@ -229,6 +229,9 @@ func (h *Handler) GetCACert(ctx context.Context) (SCEPResponse, error) { if len(certs) == 1 { response.Data = certs[0].Raw } else { + // create degenerate pkcs7 certificate structure, according to + // https://tools.ietf.org/html/rfc8894#section-4.2.1.2, because + // not signed or encrypted data has to be returned. data, err := microscep.DegenerateCertificates(certs) if err != nil { return SCEPResponse{}, err @@ -329,12 +332,11 @@ func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { w.Header().Set("Content-Type", contentHeader(response)) _, err := w.Write(response.Data) if err != nil { - writeError(w, fmt.Errorf("error when writing scep response: %w", err)) // This could end up as an error again + 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) { - // TODO: this probably needs to use SCEP specific errors (i.e. failInfo) scepError := &scep.Error{ Message: err.Error(), Status: http.StatusInternalServerError, // TODO: make this a param? diff --git a/scep/authority.go b/scep/authority.go index 2b6686fd..c73885e5 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -1,32 +1,24 @@ package scep import ( - "bytes" "context" + "crypto/subtle" "crypto/x509" - "errors" "fmt" "net/url" "github.com/smallstep/certificates/authority/provisioner" - database "github.com/smallstep/certificates/db" - - "github.com/smallstep/nosql" microx509util "github.com/micromdm/scep/crypto/x509util" microscep "github.com/micromdm/scep/scep" - //"github.com/smallstep/certificates/scep/pkcs7" + "github.com/pkg/errors" "go.mozilla.org/pkcs7" "go.step.sm/crypto/x509util" ) -var ( - certTable = []byte("scep_certs") -) - // Interface is the SCEP authority interface. type Interface interface { LoadProvisionerByID(string) (provisioner.Interface, error) @@ -42,28 +34,21 @@ type Interface interface { // Authority is the layer that handles all SCEP interactions. type Authority struct { - backdate provisioner.Duration - db nosql.DB - prefix string - dns string - - // dir *directory - + db DB + prefix string + dns string intermediateCertificate *x509.Certificate - - service *Service - signAuth SignAuthority + service *Service + signAuth SignAuthority } // AuthorityOptions required to create a new SCEP Authority. type AuthorityOptions struct { - // Service provides the SCEP functions to Authority + // Service provides the certificate chain, the signer and the decrypter to the Authority Service *Service - // Backdate - 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 + // DB is the database used by SCEP + DB DB + // DNS is 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 @@ -81,19 +66,7 @@ type SignAuthority interface { // 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 { - // If it's not a SimpleDB then go ahead and bootstrap the DB with the - // necessary SCEP tables. SimpleDB should ONLY be used for testing. - tables := [][]byte{certTable} - for _, b := range tables { - if err := ops.DB.CreateTable(b); err != nil { - return nil, fmt.Errorf("%w: error creating table %s", err, string(b)) - } - } - } - authority := &Authority{ - backdate: ops.Backdate, db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, @@ -102,7 +75,7 @@ func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { // TODO: this is not really nice to do; the Service should be removed // in its entirety to make this more interoperable with the rest of - // step-ca. + // step-ca, I think. if ops.Service != nil { authority.intermediateCertificate = ops.Service.certificateChain[0] authority.service = ops.Service @@ -200,12 +173,12 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err p7c, err := pkcs7.Parse(msg.P7.Content) if err != nil { - return err + return errors.Wrap(err, "error parsing pkcs7 content") } envelope, err := p7c.Decrypt(a.intermediateCertificate, a.service.decrypter) if err != nil { - return err + return errors.Wrap(err, "error decrypting encrypted pkcs7 content") } msg.pkiEnvelope = envelope @@ -214,19 +187,19 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err case microscep.CertRep: certs, err := microscep.CACerts(msg.pkiEnvelope) if err != nil { - return err + return errors.Wrap(err, "error extracting CA certs from pkcs7 degenerate data") } - msg.CertRepMessage.Certificate = certs[0] // TODO: check correctness of this + msg.CertRepMessage.Certificate = certs[0] return nil case microscep.PKCSReq, microscep.UpdateReq, microscep.RenewalReq: csr, err := x509.ParseCertificateRequest(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("parse CSR from pkiEnvelope: %w", err) + return errors.Wrap(err, "parse CSR from pkiEnvelope") } // check for challengePassword cp, err := microx509util.ParseChallengePassword(msg.pkiEnvelope) if err != nil { - return fmt.Errorf("scep: parse challenge password in pkiEnvelope: %w", err) + return errors.Wrap(err, "parse challenge password in pkiEnvelope") } msg.CSRReqMessage = µscep.CSRReqMessage{ RawDecrypted: msg.pkiEnvelope, @@ -235,7 +208,7 @@ func (a *Authority) DecryptPKIEnvelope(ctx context.Context, msg *PKIMessage) err } return nil case microscep.GetCRL, microscep.GetCert, microscep.CertPoll: - return fmt.Errorf("not implemented") //errNotImplemented + return errors.Errorf("not implemented") } return nil @@ -266,16 +239,13 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } // Template data - data := x509util.NewTemplateData() - data.SetCommonName(csr.Subject.CommonName) - data.SetSANs(csr.DNSNames) - data.SetCertificateRequest(csr) + data := x509util.CreateTemplateData(csr.Subject.CommonName, csr.DNSNames) // Get authorizations from the SCEP provisioner. ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOps, err := p.AuthorizeSign(ctx, "") if err != nil { - return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) + return nil, errors.Wrap(err, "error retrieving authorization options from SCEP provisioner") } opts := provisioner.SignOptions{ @@ -285,19 +255,20 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) if err != nil { - return nil, fmt.Errorf("error creating template options from SCEP provisioner: %w", err) + return nil, errors.Wrap(err, "error creating template options from SCEP provisioner") } signOps = append(signOps, templateOptions) certChain, err := a.signAuth.Sign(csr, opts, signOps...) if err != nil { - return nil, fmt.Errorf("error generating certificate for order %w", err) + return nil, errors.Wrap(err, "error generating certificate for order") } + // take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2 cert := certChain[0] - // create a degenerate cert structure - deg, err := degenerateCertificates([]*x509.Certificate{cert}) + // and create a degenerate cert structure + deg, err := microscep.DegenerateCertificates([]*x509.Certificate{cert}) if err != nil { return nil, err } @@ -370,13 +341,12 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } - // TODO: save more data? - _, err = newCert(a.db, CertOptions{ + // store the newly created certificate + err = newCert(a.db, CertOptions{ Leaf: certChain[0], Intermediates: certChain[1:], }) if err != nil { - fmt.Println(err) return nil, err } @@ -459,7 +429,7 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string) return false, err } - if p.GetChallengePassword() == password { + if subtle.ConstantTimeCompare([]byte(p.GetChallengePassword()), []byte(password)) == 1 { return true, nil } @@ -491,21 +461,3 @@ func (a *Authority) GetCACaps(ctx context.Context) []string { return caps } - -// degenerateCertificates creates degenerate certificates pkcs#7 type -func degenerateCertificates(certs []*x509.Certificate) ([]byte, error) { - var buf bytes.Buffer - for _, cert := range certs { - buf.Write(cert.Raw) - } - degenerate, err := pkcs7.DegenerateCertificate(buf.Bytes()) - if err != nil { - return nil, err - } - return degenerate, nil -} - -// Interface guards -var ( - _ Interface = (*Authority)(nil) -) diff --git a/scep/certificate.go b/scep/certificate.go index 5e43b762..39015af5 100644 --- a/scep/certificate.go +++ b/scep/certificate.go @@ -2,79 +2,20 @@ package scep import ( "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "time" - "github.com/smallstep/nosql" + "github.com/pkg/errors" ) -type certificate struct { - ID string `json:"id"` - Created time.Time `json:"created"` - Leaf []byte `json:"leaf"` - Intermediates []byte `json:"intermediates"` -} - // CertOptions options with which to create and store a cert object. type CertOptions struct { Leaf *x509.Certificate Intermediates []*x509.Certificate } -func newCert(db nosql.DB, ops CertOptions) (*certificate, error) { - - // TODO: according to the RFC this should be IssuerAndSerialNumber, - // but sscep seems to use just the serial number for getcert - - id := ops.Leaf.SerialNumber.String() - - leaf := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: ops.Leaf.Raw, - }) - var intermediates []byte - for _, cert := range ops.Intermediates { - intermediates = append(intermediates, pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: cert.Raw, - })...) - } - - cert := &certificate{ - ID: id, - Leaf: leaf, - Intermediates: intermediates, - Created: time.Now().UTC(), - } - certB, err := json.Marshal(cert) +func newCert(db DB, ops CertOptions) error { + err := db.StoreCertificate(ops.Leaf) if err != nil { - return nil, fmt.Errorf("%w: error marshaling certificate", err) - } - - _, swapped, err := db.CmpAndSwap(certTable, []byte(id), nil, certB) - switch { - case err != nil: - return nil, fmt.Errorf("%w: error storing certificate", err) - case !swapped: - return nil, fmt.Errorf("error storing certificate; " + - "value has changed since last read") - default: - return cert, nil + errors.Wrap(err, "error while storing certificate") } + return nil } - -// func getCert(db nosql.DB, id string) (*certificate, error) { -// b, err := db.Get(certTable, []byte(id)) -// if nosql.IsErrNotFound(err) { -// return nil, fmt.Errorf("certificate %s not found", id) -// } else if err != nil { -// return nil, fmt.Errorf("error loading certificate") -// } -// var cert certificate -// if err := json.Unmarshal(b, &cert); err != nil { -// return nil, fmt.Errorf("%w: error unmarshaling certificate", err) -// } -// return &cert, nil -// } diff --git a/scep/database.go b/scep/database.go new file mode 100644 index 00000000..f73573fd --- /dev/null +++ b/scep/database.go @@ -0,0 +1,7 @@ +package scep + +import "crypto/x509" + +type DB interface { + StoreCertificate(crt *x509.Certificate) error +} diff --git a/scep/options.go b/scep/options.go new file mode 100644 index 00000000..61dbe024 --- /dev/null +++ b/scep/options.go @@ -0,0 +1,31 @@ +package scep + +import ( + "crypto" + "crypto/x509" +) + +type Options struct { + // CertificateChain is the issuer certificate, along with any other bundled certificates + // to be returned in the chain for consumers. Configured in ca.json crt property. + CertificateChain []*x509.Certificate + // Signer signs CSRs in SCEP. Configured in ca.json key property. + Signer crypto.Signer `json:"-"` + // Decrypter decrypts encrypted SCEP messages. Configured in ca.json key property. + Decrypter crypto.Decrypter `json:"-"` +} + +// Validate checks the fields in Options. +func (o *Options) Validate() error { + // var typ Type + // if o == nil { + // typ = Type(SoftCAS) + // } else { + // typ = Type(o.Type) + // } + // // Check that the type can be loaded. + // if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok { + // return errors.Errorf("unsupported cas type %s", typ) + // } + return nil +} diff --git a/scep/service.go b/scep/service.go index e9a58646..508bcf77 100644 --- a/scep/service.go +++ b/scep/service.go @@ -4,9 +4,6 @@ import ( "context" "crypto" "crypto/x509" - "strings" - - "github.com/smallstep/certificates/cas/apiv1" ) // Service is a wrapper for crypto.Signer and crypto.Decrypter @@ -16,20 +13,12 @@ type Service struct { decrypter crypto.Decrypter } -func NewService(ctx context.Context, opts apiv1.Options) (*Service, error) { +func NewService(ctx context.Context, opts Options) (*Service, error) { if err := opts.Validate(); err != nil { return nil, err } - t := apiv1.Type(strings.ToLower(opts.Type)) - if t == apiv1.DefaultCAS { - t = apiv1.SoftCAS - } - - // TODO: silence the linter (temporarily) - _ = t - // TODO: should this become similar to the New CertificateAuthorityService as in x509CAService? return &Service{ certificateChain: opts.CertificateChain, From b97f024f8ab27f816068050c0fd73c5df5d285a0 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 14:02:52 +0100 Subject: [PATCH 28/42] Remove superfluous call to StoreCertificate --- ca/ca.go | 1 - scep/authority.go | 13 ------------- scep/certificate.go | 21 --------------------- scep/errors.go | 7 ------- 4 files changed, 42 deletions(-) delete mode 100644 scep/certificate.go diff --git a/ca/ca.go b/ca/ca.go index 4d268c5b..95ee6f26 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -152,7 +152,6 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { scepPrefix := "scep" scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ Service: auth.GetSCEPService(), - DB: auth.GetDatabase().(scep.DB), DNS: dns, Prefix: scepPrefix, }) diff --git a/scep/authority.go b/scep/authority.go index c73885e5..9a0a2058 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -34,7 +34,6 @@ type Interface interface { // Authority is the layer that handles all SCEP interactions. type Authority struct { - db DB prefix string dns string intermediateCertificate *x509.Certificate @@ -46,8 +45,6 @@ type Authority struct { type AuthorityOptions struct { // Service provides the certificate chain, the signer and the decrypter to the Authority Service *Service - // DB is the database used by SCEP - DB DB // DNS is 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. @@ -67,7 +64,6 @@ type SignAuthority interface { func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { authority := &Authority{ - db: ops.DB, prefix: ops.Prefix, dns: ops.DNS, signAuth: signAuth, @@ -341,15 +337,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m CertRepMessage: cr, } - // store the newly created certificate - err = newCert(a.db, CertOptions{ - Leaf: certChain[0], - Intermediates: certChain[1:], - }) - if err != nil { - return nil, err - } - return crepMsg, nil } diff --git a/scep/certificate.go b/scep/certificate.go deleted file mode 100644 index 39015af5..00000000 --- a/scep/certificate.go +++ /dev/null @@ -1,21 +0,0 @@ -package scep - -import ( - "crypto/x509" - - "github.com/pkg/errors" -) - -// CertOptions options with which to create and store a cert object. -type CertOptions struct { - Leaf *x509.Certificate - Intermediates []*x509.Certificate -} - -func newCert(db DB, ops CertOptions) error { - err := db.StoreCertificate(ops.Leaf) - if err != nil { - errors.Wrap(err, "error while storing certificate") - } - return nil -} diff --git a/scep/errors.go b/scep/errors.go index 8454e16d..4287403b 100644 --- a/scep/errors.go +++ b/scep/errors.go @@ -2,18 +2,11 @@ package scep // Error is an SCEP error type type Error struct { - // Type ProbType - // Detail string Message string `json:"message"` Status int `json:"-"` - // Sub []*Error - // Identifier *Identifier } // Error implements the error interface. func (e *Error) Error() string { - // if e.Err == nil { - // return e.Detail - // } return e.Message } From 65aab963c910cb08c989c618ef4eb1d8da198478 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:22:04 +0100 Subject: [PATCH 29/42] Add validation to SCEP Options --- scep/options.go | 66 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/scep/options.go b/scep/options.go index 61dbe024..a8aad659 100644 --- a/scep/options.go +++ b/scep/options.go @@ -2,30 +2,70 @@ package scep import ( "crypto" + "crypto/rsa" "crypto/x509" + + "github.com/pkg/errors" ) type Options struct { // CertificateChain is the issuer certificate, along with any other bundled certificates - // to be returned in the chain for consumers. Configured in ca.json crt property. + // to be returned in the chain for consumers. Configured in the ca.json crt property. CertificateChain []*x509.Certificate - // Signer signs CSRs in SCEP. Configured in ca.json key property. + // Signer signs CSRs in SCEP. Configured in the ca.json key property. Signer crypto.Signer `json:"-"` - // Decrypter decrypts encrypted SCEP messages. Configured in ca.json key property. + // Decrypter decrypts encrypted SCEP messages. Configured in the ca.json key property. Decrypter crypto.Decrypter `json:"-"` } // Validate checks the fields in Options. func (o *Options) Validate() error { - // var typ Type - // if o == nil { - // typ = Type(SoftCAS) - // } else { - // typ = Type(o.Type) - // } - // // Check that the type can be loaded. - // if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok { - // return errors.Errorf("unsupported cas type %s", typ) - // } + + if o.CertificateChain == nil { + return errors.New("certificate chain not configured correctly") + } + + if len(o.CertificateChain) < 1 { + return errors.New("certificate chain should at least have one certificate") + } + + // According to the RFC: https://tools.ietf.org/html/rfc8894#section-3.1, SCEP + // can be used with something different than RSA, but requires the encryption + // to be performed using the challenge password. An older version of specification + // states that only RSA is supported: https://tools.ietf.org/html/draft-nourse-scep-23#section-2.1.1 + // Other algorithms than RSA do not seem to be supported in certnanny/sscep, but it might work + // in micromdm/scep. Currently only RSA is allowed, but it might be an option + // to try other algorithms in the future. + intermediate := o.CertificateChain[0] + if intermediate.PublicKeyAlgorithm != x509.RSA { + return errors.New("only the RSA algorithm is (currently) supported") + } + + // TODO: add checks for key usage? + + signerPublicKey, ok := o.Signer.Public().(*rsa.PublicKey) + if !ok { + return errors.New("only RSA public keys are (currently) supported as signers") + } + + // check if the intermediate ca certificate has the same public key as the signer. + // According to the RFC it seems valid to have different keys for the intermediate + // and the CA signing new certificates, so this might change in the future. + if !signerPublicKey.Equal(intermediate.PublicKey) { + return errors.New("mismatch between certificate chain and signer public keys") + } + + decrypterPublicKey, ok := o.Decrypter.Public().(*rsa.PublicKey) + if !ok { + return errors.New("only RSA public keys are (currently) supported as decrypters") + } + + // check if intermedate public key is the same as the decrypter public key. + // In certnanny/sscep it's mentioned that the signing key can be different + // from the decrypting (and encrypting) key. Currently that's not supported. + if !decrypterPublicKey.Equal(intermediate.PublicKey) { + return errors.New("mismatch between certificate chain and decrypter public keys") + } + return nil } From 69d701062adef97502e1a148eae63a45fe591604 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:24:27 +0100 Subject: [PATCH 30/42] Fix typo --- scep/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scep/options.go b/scep/options.go index a8aad659..752b309a 100644 --- a/scep/options.go +++ b/scep/options.go @@ -60,7 +60,7 @@ func (o *Options) Validate() error { return errors.New("only RSA public keys are (currently) supported as decrypters") } - // check if intermedate public key is the same as the decrypter public key. + // check if intermediate public key is the same as the decrypter public key. // In certnanny/sscep it's mentioned that the signing key can be different // from the decrypting (and encrypting) key. Currently that's not supported. if !decrypterPublicKey.Equal(intermediate.PublicKey) { From b815478981c2c062096834717c17c1f300aa1278 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 15:44:45 +0100 Subject: [PATCH 31/42] Make serving SCEP endpoints optional Only when a SCEP provisioner is enabled, the SCEP endpoints will now be available. The SCEP endpoints will be served on an "insecure" server, without TLS, only when an additional "insecureAddress" and a SCEP provisioner are configured for the CA. --- authority/authority.go | 2 ++ ca/ca.go | 72 ++++++++++++++++++++++++++---------------- scep/scep.go | 1 - 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index e5b62602..a2dca6e0 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -440,6 +440,8 @@ func (a *Authority) CloseForReload() { // requiresDecrypter returns whether the Authority // requires a KMS that provides a crypto.Decrypter +// Currently this is only required when SCEP is +// enabled. func (a *Authority) requiresDecrypter() bool { return a.requiresSCEPService() } diff --git a/ca/ca.go b/ca/ca.go index b6b1fc7f..84545f57 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -115,6 +115,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { if err != nil { return nil, err } + ca.auth = auth tlsConfig, err := ca.getTLSConfig(auth) if err != nil { @@ -166,29 +167,35 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { acmeRouterHandler.Route(r) }) - 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) - mux.Route("/"+scepPrefix, func(r chi.Router) { - scepRouterHandler.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. To my current understanding there's no strong reason - // to not 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. - insecureMux.Route("/"+scepPrefix, func(r chi.Router) { - scepRouterHandler.Route(r) - }) + // 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) @@ -213,14 +220,15 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { insecureHandler = logger.Middleware(insecureHandler) } - ca.auth = auth ca.srv = server.New(config.Address, handler, tlsConfig) - // TODO: instead opt for having a single server.Server but two - // http.Servers handling the HTTP and HTTPS handler? The latter - // will probably introduce more complexity in terms of graceful - // reload. - if config.InsecureAddress != "" { + // only start the insecure server if the insecure address is configured + // and, currently, also only when it should serve SCEP endpoints. + if ca.shouldServeSCEPEndpoints() && config.InsecureAddress != "" { + // TODO: instead opt for having a single server.Server but two + // http.Servers handling the HTTP and HTTPS handler? The latter + // will probably introduce more complexity in terms of graceful + // reload. ca.insecureSrv = server.New(config.InsecureAddress, insecureHandler, nil) } @@ -375,6 +383,14 @@ 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 // diff --git a/scep/scep.go b/scep/scep.go index f56176d7..3323ac1d 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -33,7 +33,6 @@ var ( oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} //oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} - ) // PKIMessage defines the possible SCEP message types From 9bda3c465ae7a0f094a45bad73b5e8273cdd2dae Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 16:11:35 +0100 Subject: [PATCH 32/42] Add more template data --- scep/authority.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/scep/authority.go b/scep/authority.go index 9a0a2058..3443eb51 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -4,7 +4,6 @@ import ( "context" "crypto/subtle" "crypto/x509" - "fmt" "net/url" "github.com/smallstep/certificates/authority/provisioner" @@ -109,9 +108,9 @@ func (a *Authority) GetLinkExplicit(provName string, abs bool, baseURL *url.URL, // URL dynamically obtained from the request for which the link is being calculated. func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *url.URL, inputs ...string) string { - // TODO: do we need to provide a way to provide a different suffix/base? + // TODO: do we need to provide a way to provide a different suffix? // Like "/cgi-bin/pkiclient.exe"? Or would it be enough to have that as the name? - link := fmt.Sprintf("/%s", provisionerName) + link := "/" + provisionerName if abs { // Copy the baseURL value from the pointer. https://github.com/golang/go/issues/38351 @@ -235,7 +234,31 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } // Template data - data := x509util.CreateTemplateData(csr.Subject.CommonName, csr.DNSNames) + sans := []string{} + sans = append(sans, csr.DNSNames...) + sans = append(sans, csr.EmailAddresses...) + for _, v := range csr.IPAddresses { + sans = append(sans, v.String()) + } + for _, v := range csr.URIs { + sans = append(sans, v.String()) + } + if len(sans) == 0 { + sans = append(sans, csr.Subject.CommonName) + } + data := x509util.CreateTemplateData(csr.Subject.CommonName, sans) + data.SetCertificateRequest(csr) + data.SetSubject(x509util.Subject{ + Country: csr.Subject.Country, + Organization: csr.Subject.Organization, + OrganizationalUnit: csr.Subject.OrganizationalUnit, + Locality: csr.Subject.Locality, + Province: csr.Subject.Province, + StreetAddress: csr.Subject.StreetAddress, + PostalCode: csr.Subject.PostalCode, + SerialNumber: csr.Subject.SerialNumber, + CommonName: csr.Subject.CommonName, + }) // Get authorizations from the SCEP provisioner. ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) From 2320d0911ea97d49f5dea5fe92d4a603c990927c Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 16:21:02 +0100 Subject: [PATCH 33/42] Add sync.WaitGroup for proper error handling in Run() --- ca/ca.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index 84545f57..6452d9f5 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "reflect" + "sync" "github.com/go-chi/chi" "github.com/pkg/errors" @@ -238,18 +239,28 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) { // Run starts the CA calling to the server ListenAndServe method. func (ca *CA) Run() error { + var wg sync.WaitGroup errors := make(chan error, 1) - go func() { - if ca.insecureSrv != nil { + + if ca.insecureSrv != nil { + wg.Add(1) + go func() { + defer wg.Done() errors <- ca.insecureSrv.ListenAndServe() - } - }() + }() + } + + wg.Add(1) go func() { + defer wg.Done() errors <- ca.srv.ListenAndServe() }() // wait till error occurs; ensures the servers keep listening err := <-errors + + wg.Wait() + return err } From c3d9cef49791f56fd103e37f9233db2c362e12cf Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 26 Mar 2021 22:04:18 +0100 Subject: [PATCH 34/42] Update to v2.0.0 of github.com/micromdm/scep --- go.mod | 3 +- go.sum | 240 +++++++++++++++++++++++++++++++++++++++++++++- scep/api/api.go | 2 +- scep/authority.go | 4 +- scep/scep.go | 2 +- 5 files changed, 244 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index e9458bee..6a6fd7cf 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,13 @@ require ( github.com/ThalesIgnite/crypto11 v1.2.4 github.com/aws/aws-sdk-go v1.30.29 github.com/go-chi/chi v4.0.2+incompatible + github.com/go-kit/kit v0.10.0 // indirect github.com/go-piv/piv-go v1.7.0 github.com/golang/mock v1.4.4 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.1-0.20210219005251-6027e7654b23 + github.com/micromdm/scep/v2 v2.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 06be37a7..1c6a3d0a 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= @@ -55,38 +56,68 @@ github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TN github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8= github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -97,19 +128,30 @@ github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc= github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= @@ -119,16 +161,31 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-piv/piv-go v1.7.0 h1:rfjdFdASfGV5KLJhSjgpGJ5lzVZVtRWn8ovy/H9HQ/U= github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= @@ -156,6 +213,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -171,6 +229,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= @@ -186,22 +245,51 @@ github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35 h1:WL9iUw2tSwvaCb3++2 github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -209,13 +297,22 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -231,9 +328,12 @@ github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= @@ -241,36 +341,98 @@ github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEX github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23 h1:QACkVsQ7Qx4PuPDFL2OFD5u4OnYT0TkReWk9IVqaGHA= -github.com/micromdm/scep v1.0.1-0.20210219005251-6027e7654b23/go.mod h1:a82oNZyGV8jj9Do7dh8EkA90+esBls0CZHR6B85Qda8= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/micromdm/scep/v2 v2.0.0 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA= +github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU= github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk= github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= @@ -279,10 +441,14 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo= github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= @@ -290,6 +456,10 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/nosql v0.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po= github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -298,14 +468,19 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -316,11 +491,15 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -328,8 +507,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -342,10 +525,20 @@ go.step.sm/cli-utils v0.2.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6 go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= go.step.sm/crypto v0.8.0 h1:S4qBPyy3hR7KWLybSkHB0H14pwFfYkom4RZ96JzmXig= go.step.sm/crypto v0.8.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -390,16 +583,24 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -434,7 +635,13 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -448,9 +655,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -479,16 +688,20 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -498,6 +711,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -505,6 +720,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -531,6 +747,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -550,6 +767,7 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw= google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -561,6 +779,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -588,10 +807,15 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 h1:bFFRpT+e8JJVY7lMMfvezL1ZIwqiwmPl2bsE2yx4HqM= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -614,17 +838,27 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -639,3 +873,5 @@ rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/scep/api/api.go b/scep/api/api.go index cff3d32b..c19c090f 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -18,7 +18,7 @@ import ( "github.com/pkg/errors" - microscep "github.com/micromdm/scep/scep" + microscep "github.com/micromdm/scep/v2/scep" ) const ( diff --git a/scep/authority.go b/scep/authority.go index 3443eb51..34913a84 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -8,8 +8,8 @@ import ( "github.com/smallstep/certificates/authority/provisioner" - microx509util "github.com/micromdm/scep/crypto/x509util" - microscep "github.com/micromdm/scep/scep" + microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util" + microscep "github.com/micromdm/scep/v2/scep" "github.com/pkg/errors" diff --git a/scep/scep.go b/scep/scep.go index 3323ac1d..afabf368 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -4,7 +4,7 @@ import ( "crypto/x509" "encoding/asn1" - microscep "github.com/micromdm/scep/scep" + microscep "github.com/micromdm/scep/v2/scep" //"github.com/smallstep/certificates/scep/pkcs7" From 9787728fbd73ea04b70de56cdb75b8ac8ed5e61f Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 16 Apr 2021 14:09:34 +0200 Subject: [PATCH 35/42] Mask challenge password after it has been read --- authority/provisioner/scep.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 6af3dc83..7f3cce8f 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -20,6 +20,8 @@ type SCEP struct { Options *Options `json:"options,omitempty"` Claims *Claims `json:"claims,omitempty"` claimer *Claimer + + secretChallengePassword string } // GetID returns the provisioner unique identifier. @@ -73,6 +75,10 @@ func (s *SCEP) Init(config Config) (err error) { return err } + // Mask the actual challenge value, so it won't be marshalled + s.secretChallengePassword = s.ChallengePassword + s.ChallengePassword = "*** redacted ***" + // TODO: add other, SCEP specific, options? return err @@ -95,7 +101,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // GetChallengePassword returns the challenge password func (s *SCEP) GetChallengePassword() string { - return s.ChallengePassword + return s.secretChallengePassword } // GetCapabilities returns the CA capabilities From 2336936b5c9e256283b8caa79d5bed4739b1a6ee Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 16 Apr 2021 15:49:33 +0200 Subject: [PATCH 36/42] Fix typo --- authority/provisioner/scep.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 7f3cce8f..49f53860 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -75,7 +75,7 @@ func (s *SCEP) Init(config Config) (err error) { return err } - // Mask the actual challenge value, so it won't be marshalled + // Mask the actual challenge value, so it won't be marshaled s.secretChallengePassword = s.ChallengePassword s.ChallengePassword = "*** redacted ***" From ff1b46c95d70a440e2e6578838f9267f4fd0a15f Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 6 May 2021 22:56:28 +0200 Subject: [PATCH 37/42] Add configuration option for specifying the minimum public key length Instead of using the defaultPublicKeyValidator a new validator called publicKeyMinimumLengthValidator has been implemented that uses a configurable minimum length for public keys in CSRs. It's also an option to alter the defaultPublicKeyValidator to also take a parameter, but that would touch quite some lines of code. This might be a viable option after merging SCEP support. --- authority/provisioner/scep.go | 20 +++++++++++++---- authority/provisioner/sign_options.go | 31 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index 49f53860..a5825b9b 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -2,6 +2,7 @@ package provisioner import ( "context" + "fmt" "time" "github.com/pkg/errors" @@ -17,9 +18,11 @@ 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 + // MinimumPublicKeyLength is the minimum length for public keys in CSRs + MinimumPublicKeyLength int `json:"minimumPublicKeyLength,omitempty"` + Options *Options `json:"options,omitempty"` + Claims *Claims `json:"claims,omitempty"` + claimer *Claimer secretChallengePassword string } @@ -79,6 +82,15 @@ func (s *SCEP) Init(config Config) (err error) { s.secretChallengePassword = s.ChallengePassword s.ChallengePassword = "*** redacted ***" + // Default to 2048 bits minimum public key length (for CSRs) if not set + if s.MinimumPublicKeyLength == 0 { + s.MinimumPublicKeyLength = 2048 + } + + if s.MinimumPublicKeyLength%8 != 0 { + return fmt.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + } + // TODO: add other, SCEP specific, options? return err @@ -94,7 +106,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e newForceCNOption(s.ForceCN), profileDefaultDuration(s.claimer.DefaultTLSCertDuration()), // validators - defaultPublicKeyValidator{}, + newPublicKeyMinimumLengthValidator(s.MinimumPublicKeyLength), newValidityValidator(s.claimer.MinTLSCertDuration(), s.claimer.MaxTLSCertDuration()), }, nil } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 3b52d497..20b6f8c0 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -8,6 +8,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/json" + "fmt" "net" "net/url" "reflect" @@ -117,6 +118,36 @@ func (v defaultPublicKeyValidator) Valid(req *x509.CertificateRequest) error { return nil } +// publicKeyMinimumLengthValidator validates the length (in bits) of the public key +// of a certificate request is at least a certain length +type publicKeyMinimumLengthValidator struct { + length int +} + +// newPublicKeyMinimumLengthValidator creates a new publicKeyMinimumLengthValidator +// with the given length as its minimum value +// TODO: change the defaultPublicKeyValidator to have a configurable length instead? +func newPublicKeyMinimumLengthValidator(length int) publicKeyMinimumLengthValidator { + return publicKeyMinimumLengthValidator{ + length: length, + } +} + +// Valid checks that certificate request common name matches the one configured. +func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) error { + switch k := req.PublicKey.(type) { + case *rsa.PublicKey: + minimumLengthInBytes := v.length / 8 + if k.Size() < minimumLengthInBytes { + return fmt.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) + } + case *ecdsa.PublicKey, ed25519.PublicKey: + default: + return errors.Errorf("unrecognized public key of type '%T' in CSR", k) + } + return nil +} + // commonNameValidator validates the common name of a certificate request. type commonNameValidator string From d0a9cbc797fdf23dfe334d0677b3d23f9fac61cd Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:22:06 +0200 Subject: [PATCH 38/42] Change fmt to errors package for formatting errors --- authority/provisioner/scep.go | 3 +-- authority/provisioner/sign_options.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index a5825b9b..bedf1733 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -2,7 +2,6 @@ package provisioner import ( "context" - "fmt" "time" "github.com/pkg/errors" @@ -88,7 +87,7 @@ func (s *SCEP) Init(config Config) (err error) { } if s.MinimumPublicKeyLength%8 != 0 { - return fmt.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) } // TODO: add other, SCEP specific, options? diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 20b6f8c0..764916b6 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -8,7 +8,6 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/json" - "fmt" "net" "net/url" "reflect" @@ -139,7 +138,7 @@ func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) err case *rsa.PublicKey: minimumLengthInBytes := v.length / 8 if k.Size() < minimumLengthInBytes { - return fmt.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) + return errors.Errorf("rsa key in CSR must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) } case *ecdsa.PublicKey, ed25519.PublicKey: default: From 54610e890b26b0a38ccae4c6ea572a2b89fd63ee Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:23:09 +0200 Subject: [PATCH 39/42] Improve error logging --- scep/api/api.go | 28 +++++++++++++++++----------- scep/authority.go | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/scep/api/api.go b/scep/api/api.go index c19c090f..e64eef83 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -51,6 +51,7 @@ type SCEPResponse struct { CACertNum int Data []byte Certificate *x509.Certificate + Error error } // Handler is the SCEP request handler. @@ -75,7 +76,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, errors.Wrap(err, "not a scep get request")) + writeError(w, errors.Wrap(err, "invalid scep get request")) return } @@ -94,7 +95,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { } if err != nil { - writeError(w, errors.Wrap(err, "get request failed")) + writeError(w, errors.Wrap(err, "scep get request failed")) return } @@ -106,7 +107,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { request, err := decodeSCEPRequest(r) if err != nil { - writeError(w, errors.Wrap(err, "not a scep post request")) + writeError(w, errors.Wrap(err, "invalid scep post request")) return } @@ -121,7 +122,7 @@ func (h *Handler) Post(w http.ResponseWriter, r *http.Request) { } if err != nil { - writeError(w, errors.Wrap(err, "post request failed")) + writeError(w, errors.Wrap(err, "scep post request failed")) return } @@ -193,13 +194,13 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP { p, err := h.Auth.LoadProvisionerByID("scep/" + provisionerID) if err != nil { - writeError(w, err) + api.WriteError(w, err) return } provisioner, ok := p.(*provisioner.SCEP) if !ok { - writeError(w, errors.New("provisioner must be of type SCEP")) + api.WriteError(w, errors.New("provisioner must be of type SCEP")) return } @@ -293,12 +294,12 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe challengeMatches, err := h.Auth.MatchChallengePassword(ctx, msg.CSRReqMessage.ChallengePassword) if err != nil { - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when checking password") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("error when checking password")) } if !challengeMatches { // TODO: can this be returned safely to the client? In the end, if the password was correct, that gains a bit of info too. - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "wrong password provided") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.New("wrong password provided")) } } @@ -306,7 +307,7 @@ func (h *Handler) PKIOperation(ctx context.Context, request SCEPRequest) (SCEPRe certRep, err := h.Auth.SignCSR(ctx, csr, msg) if err != nil { - return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, "error when signing new certificate") + return h.createFailureResponse(ctx, csr, msg, microscep.BadRequest, errors.Wrap(err, "error when signing new certificate")) } response := SCEPResponse{ @@ -325,6 +326,10 @@ func formatCapabilities(caps []string) []byte { // writeSCEPResponse writes a SCEP response back to the SCEP client. func writeSCEPResponse(w http.ResponseWriter, response SCEPResponse) { + if response.Error != nil { + api.LogError(w, response.Error) + } + if response.Certificate != nil { api.LogCertificate(w, response.Certificate) } @@ -344,14 +349,15 @@ func writeError(w http.ResponseWriter, err error) { api.WriteError(w, scepError) } -func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, infoText string) (SCEPResponse, error) { - certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) +func (h *Handler) createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info microscep.FailInfo, failError error) (SCEPResponse, error) { + certRepMsg, err := h.Auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), failError.Error()) if err != nil { return SCEPResponse{}, err } return SCEPResponse{ Operation: opnPKIOperation, Data: certRepMsg.Raw, + Error: failError, }, nil } diff --git a/scep/authority.go b/scep/authority.go index 34913a84..47d1d41c 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -416,7 +416,7 @@ func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.Certifi cr := &CertRepMessage{ PKIStatus: microscep.FAILURE, - FailInfo: microscep.BadRequest, + FailInfo: microscep.FailInfo(info), RecipientNonce: microscep.RecipientNonce(msg.SenderNonce), } From a3ec890e715afe2c677285609b68aefbe3d945a5 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 00:31:34 +0200 Subject: [PATCH 40/42] Fix small typo in divisible --- authority/provisioner/scep.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index bedf1733..7673ecc2 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -87,7 +87,7 @@ func (s *SCEP) Init(config Config) (err error) { } if s.MinimumPublicKeyLength%8 != 0 { - return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisibly by 8", s.MinimumPublicKeyLength) + return errors.Errorf("only minimum public keys exactly divisible by 8 are supported; %d is not exactly divisible by 8", s.MinimumPublicKeyLength) } // TODO: add other, SCEP specific, options? From 877fc9ae8ce5135412e87628d0750ac66587f31a Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 7 May 2021 15:32:07 +0200 Subject: [PATCH 41/42] Add tests for CreateDecrypter --- kms/softkms/softkms_test.go | 69 +++++++++++++++++++++++++++++++ kms/softkms/testdata/rsa.priv.pem | 30 ++++++++++++++ kms/softkms/testdata/rsa.pub.pem | 9 ++++ 3 files changed, 108 insertions(+) create mode 100644 kms/softkms/testdata/rsa.priv.pem create mode 100644 kms/softkms/testdata/rsa.pub.pem diff --git a/kms/softkms/softkms_test.go b/kms/softkms/softkms_test.go index 607a5a51..9e293b07 100644 --- a/kms/softkms/softkms_test.go +++ b/kms/softkms/softkms_test.go @@ -310,3 +310,72 @@ func Test_generateKey(t *testing.T) { }) } } + +func TestSoftKMS_CreateDecrypter(t *testing.T) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + pemBlock, err := pemutil.Serialize(privateKey) + if err != nil { + t.Fatal(err) + } + pemBlockPassword, err := pemutil.Serialize(privateKey, pemutil.WithPassword([]byte("pass"))) + if err != nil { + t.Fatal(err) + } + ecdsaPK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + ecdsaPemBlock, err := pemutil.Serialize(ecdsaPK) + if err != nil { + t.Fatal(err) + } + b, err := ioutil.ReadFile("testdata/rsa.priv.pem") + if err != nil { + t.Fatal(err) + } + block, _ := pem.Decode(b) + block.Bytes, err = x509.DecryptPEMBlock(block, []byte("pass")) //nolint + if err != nil { + t.Fatal(err) + } + keyFromFile, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + t.Fatal(err) + } + type args struct { + req *apiv1.CreateDecrypterRequest + } + tests := []struct { + name string + args args + want crypto.Decrypter + wantErr bool + }{ + {"decrypter", args{&apiv1.CreateDecrypterRequest{Decrypter: privateKey}}, privateKey, false}, + {"file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.priv.pem", Password: []byte("pass")}}, keyFromFile, false}, + {"pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlock)}}, privateKey, false}, + {"pem password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("pass")}}, privateKey, false}, + {"fail none", args{&apiv1.CreateDecrypterRequest{}}, nil, true}, + {"fail missing", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/missing"}}, nil, true}, + {"fail bad pem", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: []byte("bad pem")}}, nil, true}, + {"fail bad password", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(pemBlockPassword), Password: []byte("bad-pass")}}, nil, true}, + {"fail not a decrypter (ecdsa key)", args{&apiv1.CreateDecrypterRequest{DecryptionKeyPEM: pem.EncodeToMemory(ecdsaPemBlock)}}, nil, true}, + {"fail not a decrypter from file", args{&apiv1.CreateDecrypterRequest{DecryptionKey: "testdata/rsa.pub.pem"}}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &SoftKMS{} + got, err := k.CreateDecrypter(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("SoftKMS.CreateDecrypter(), error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SoftKMS.CreateDecrypter() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kms/softkms/testdata/rsa.priv.pem b/kms/softkms/testdata/rsa.priv.pem new file mode 100644 index 00000000..497ec8d2 --- /dev/null +++ b/kms/softkms/testdata/rsa.priv.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,dff7bfd0e0163a4cd7ade8f68b966699 + +jtmOhr2zo244Oq2fVsShZAUoQZ1gi6Iwc4i0sReU66XP9CFkdvJasAfkjQGrbCEy +m2+r7W6aH+L3j/4sXcJe8h4UVnnC4DHCozmtqqFCq7cFS4TiVpco26wEVH5WLm7Y +3Ew/pL0k24E+Ycf+yV5c1tQXRlmsKubjwzrZtGZP2yn3Dxsu97mzOXAfx7r+DIKI +5a4S3m1/yXw76tt6Iho9h4huA25UUDHKUQvOGd5gmOKqJRV9djoyu85ODbmz5nt0 +pB2EzdHOrefgd0rcQQPI1uFBWqASJxTn+uS7ZBP4rlCcs932lI1mPerMh1ujo51F +3aibrwhKE6kaJyOOnUbvyBnaiTb5i4WwTqx/jfsOsggXQb3UlxgDph48VXw8O2jF +CQmle+TR8yr1A14/Dno5Dd4cqPv6AmWWU2zolvLxKQixFcvjsyQYCDajWWRPkOgj +RTKXDqL1mpjrlDqcSXzemCWk6FzqdUQhimhFgARDRfRwwDeWQN5ua4a3gnem/cpA +ZS8J45H0ZC/CxGPfp+qx75n5a875+n4VMmCZerXPzEIj1CzS7D6BVAXTHJaNIB6S +0WNfQnftp09O2l6iXBE+MHt5bVxqt46+vgcceSu7Gsb3ZfD79vnQ7tR+wb+xmHKk +8rVcMrB+kDRXVguH/a3zUGYAEnb6hPkIJywJVD4G65oM+D9D67Mdka8wIMK48doV +my8a0MfT/9AidR6XJVxIkHlPsPzlxirm/NKF7oSlzurcvYcPAYnHYLW2uB8dyidq +1zB+3rxbSYCVqrhqzN4prydGvkIE3/+AJyIGn7uGSTSSyF6BC9APXQaHplRGKwLz +efOIMoEwXJ1DIcKmk9GB65xxrZxMu3Cclcbc4PgY4370G0PfCHuUQNQL2RUWCQn0 +aax+qDiFg1LsLRaI75OaLJ+uKs6rRfytQMmFGqK/b6iVbktiYWMtrDJDo4OUTtZ6 +LBBySH7sAFgI3IIxct2Fwg8X1J4kfHr9jWTLjMEIE2o8cyqvSQ8rdwA25MxRcn75 +DGqSlGE6Sx0XhWCVUiZidVRSYGKmOmH9yw8cjKm17qL23t8Gwns4Xunl7V6YlTCG +BPw5f1jWCQ94TwvUSuHMPYoXlYwRoe+jfDAzp2AQwXqvWX5Qno5PKz9gQ5iYacZ/ +k82fyPbk2XLDkPnaNJKnyiIc252O0WffUlX6Rlv3aF8ZgVvWfZbuHEK6g1W+IKSA +pXAQ+iZBl+fjs/wT0yZSNTB0P1InD9Ve536L94gxXoeMr6F0Eouk3J2R9qdFp0Av +31xylRKSmzUf87/sRxjy3FzSTjIal77y1euJoAEU/nShmNrAZ6B8wnlvHfVwbgmt +xWqxYIi/j/C8Led9uhEhX2WjPsO7ckGA41Tw6hZk/5hr4jmPoZQKHf9OauJFujMh +ybPRQ6SGZJaYQAgpEGHSHFm8lwf5/DcezdSMdzqAKBWJBv6MediMuS60wcJ0Tebk +rdLkNE4bsxfc889BkXBrSqfd+Auu5RcF/kF44gLL7oj4ojQyV44vLZbC4+liGThT +bhayYGV64hsY+zL03u5wVfF1Y+33/uc8o/0JjbfuW5AIdikVES/jnKKFXSTMNL69 +-----END RSA PRIVATE KEY----- diff --git a/kms/softkms/testdata/rsa.pub.pem b/kms/softkms/testdata/rsa.pub.pem new file mode 100644 index 00000000..4bf5de9b --- /dev/null +++ b/kms/softkms/testdata/rsa.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn2Oh7/uWB5RH40la1a43 +IRaLZ8EnJVw5DCKE3BUre8xflVY2wTIS7XHcY0fEGprtq7hzFKors9AIGGn2yGrf +bZX2I+1g+RtQ6cLL6koeLuhRDqCuae0lZPulWc5ixBmM9mpl4ARRcpQFldxFRhis +xUaHMx8VqdZjFSDc5CJHYYK1n2G5DyuzJCk6yOfyMpwxizZJF4IUyqV7zKmZv1z9 +/Xd8X0ag7jRdaTBpupJ1WLaq7LlvyB4nr47JXXkLFbRIL1F/gTcPtg0tdEZiKnxs +VLKwOs3VjhEorUwhmVxr4NnNX/0tuOY1FJ0mx5jKLAevqLVwK2JIg/f3h7JcNxDy +tQIDAQAB +-----END PUBLIC KEY----- From 375687cd1b834cd7fc72d2e7f32f0e261a7570c4 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 20 May 2021 21:31:52 +0200 Subject: [PATCH 42/42] Add setup for Authority tests --- authority/authority_test.go | 148 +++++++++++++++++++++++ authority/testdata/scep/intermediate.crt | 15 +++ authority/testdata/scep/intermediate.key | 30 +++++ authority/testdata/scep/root.crt | 10 ++ authority/testdata/scep/root.key | 8 ++ 5 files changed, 211 insertions(+) create mode 100644 authority/testdata/scep/intermediate.crt create mode 100644 authority/testdata/scep/intermediate.key create mode 100644 authority/testdata/scep/root.crt create mode 100644 authority/testdata/scep/root.key diff --git a/authority/authority_test.go b/authority/authority_test.go index e6625d6a..618e7939 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" + "fmt" "io/ioutil" "net" "reflect" @@ -320,3 +321,150 @@ func TestAuthority_CloseForReload(t *testing.T) { }) } } + +func testScepAuthority(t *testing.T, opts ...Option) *Authority { + + p := provisioner.List{ + &provisioner.SCEP{ + Name: "scep1", + Type: "SCEP", + }, + } + c := &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "pass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + } + a, err := New(c, opts...) + assert.FatalError(t, err) + return a +} + +func TestAuthority_GetSCEPService(t *testing.T) { + auth := testScepAuthority(t) + fmt.Println(auth) + + p := provisioner.List{ + &provisioner.SCEP{ + Name: "scep1", + Type: "SCEP", + }, + } + + type fields struct { + config *Config + // keyManager kms.KeyManager + // provisioners *provisioner.Collection + // db db.AuthDB + // templates *templates.Templates + // x509CAService cas.CertificateAuthorityService + // rootX509Certs []*x509.Certificate + // federatedX509Certs []*x509.Certificate + // certificates *sync.Map + // scepService *scep.Service + // sshCAUserCertSignKey ssh.Signer + // sshCAHostCertSignKey ssh.Signer + // sshCAUserCerts []ssh.PublicKey + // sshCAHostCerts []ssh.PublicKey + // sshCAUserFederatedCerts []ssh.PublicKey + // sshCAHostFederatedCerts []ssh.PublicKey + // initOnce bool + // startTime time.Time + // sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error) + // sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error) + // sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error) + // getIdentityFunc provisioner.GetIdentityFunc + } + tests := []struct { + name string + fields fields + wantService bool + wantErr bool + }{ + { + name: "ok", + fields: fields{ + config: &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "pass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + }, + }, + wantService: true, + wantErr: false, + }, + { + name: "wrong password", + fields: fields{ + config: &Config{ + Address: "127.0.0.1:8443", + InsecureAddress: "127.0.0.1:8080", + Root: []string{"testdata/scep/root.crt"}, + IntermediateCert: "testdata/scep/intermediate.crt", + IntermediateKey: "testdata/scep/intermediate.key", + DNSNames: []string{"example.com"}, + Password: "wrongpass", + AuthorityConfig: &AuthConfig{ + Provisioners: p, + }, + }, + }, + wantService: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // a := &Authority{ + // config: tt.fields.config, + // keyManager: tt.fields.keyManager, + // provisioners: tt.fields.provisioners, + // db: tt.fields.db, + // templates: tt.fields.templates, + // x509CAService: tt.fields.x509CAService, + // rootX509Certs: tt.fields.rootX509Certs, + // federatedX509Certs: tt.fields.federatedX509Certs, + // certificates: tt.fields.certificates, + // scepService: tt.fields.scepService, + // sshCAUserCertSignKey: tt.fields.sshCAUserCertSignKey, + // sshCAHostCertSignKey: tt.fields.sshCAHostCertSignKey, + // sshCAUserCerts: tt.fields.sshCAUserCerts, + // sshCAHostCerts: tt.fields.sshCAHostCerts, + // sshCAUserFederatedCerts: tt.fields.sshCAUserFederatedCerts, + // sshCAHostFederatedCerts: tt.fields.sshCAHostFederatedCerts, + // initOnce: tt.fields.initOnce, + // startTime: tt.fields.startTime, + // sshBastionFunc: tt.fields.sshBastionFunc, + // sshCheckHostFunc: tt.fields.sshCheckHostFunc, + // sshGetHostsFunc: tt.fields.sshGetHostsFunc, + // getIdentityFunc: tt.fields.getIdentityFunc, + // } + a, err := New(tt.fields.config) + fmt.Println(err) + fmt.Println(a) + if (err != nil) != tt.wantErr { + t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantService { + if got := a.GetSCEPService(); (got != nil) != tt.wantService { + t.Errorf("Authority.GetSCEPService() = %v, wantService %v", got, tt.wantService) + } + } + }) + } +} diff --git a/authority/testdata/scep/intermediate.crt b/authority/testdata/scep/intermediate.crt new file mode 100644 index 00000000..42ac4867 --- /dev/null +++ b/authority/testdata/scep/intermediate.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZTCCAgugAwIBAgIQDPpOQXW7OLMFNR/+iOUdQjAKBggqhkjOPQQDAjAXMRUw +EwYDVQQDEwxzY2VwdGVzdHJvb3QwHhcNMjEwNTA3MTUyMjU2WhcNMzEwNTA1MTUy +MjU2WjAfMR0wGwYDVQQDExRzY2VwdGVzdGludGVybWVkaWF0ZTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJTw49z9/MeZ/YeRO89ylMV3HnYpw52/Vs2G +NsgYZRKiPz2RjixUp1iWRPoDONdlEOIAo0TALNOqz4EqJHB+FpBPBA1ZfwG/PlP/ +eWFubNXLXIhZPSQOiHmL4dIw0FS/VFGZm1eqc9JPG/V2G6UaKvOa8+W9/nhi4eeL ++/9nTwG4cTav9ltaVxQ55kcoJtMcvouYQ4oPSZ6yNuVYbFAoaqZnJqNQhxDvKsFH +lHmvl28FAVM+otmEQNTm91uPwXuVusxEGn9N/d7M4iojCiMGg0S3luBS8IrGRI1Y +bSKZvGsFnqUjHh2cLL1lqqo5+QvhvP9ut6+g8QGoq8NTc2yCRy8CAwEAAaNmMGQw +DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGfO +jTNTKTAyra+rAd/NL2ydarSFMB8GA1UdIwQYMBaAFKJr1p5QRfkHzewG3YEhPAtv +FQNrMAoGCCqGSM49BAMCA0gAMEUCIEYK76FN9a/hWkMZcQ+NXyzGtfW+bnwsX3oN +wT6jfyO0AiEAojTeSwf/H2l/E1lvsWJfNr8nOokWz+ZsbmMm5PU0Y+g= +-----END CERTIFICATE----- diff --git a/authority/testdata/scep/intermediate.key b/authority/testdata/scep/intermediate.key new file mode 100644 index 00000000..ae564056 --- /dev/null +++ b/authority/testdata/scep/intermediate.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,a54ae9388ce050f0a479a258d105fbb7 + +VkJp9kKZQ7O9Gy9orvXaO+klt4Lrqp9oSABSBy8yFcc3neniLixqcyZZ4+CC/OG2 +TGTm4TiB9RBucrUyPwoxBraWbtTLHvS4nfPwr2feSTKoHDhSIr4Z1VMDF8PWiOSg +vD3iYs5F1lz78hcB/SNdSZ2jm0ze84DFC2E49agWeiFLwezcLhXKQ2HHRJ6PmJv7 +IYB7+aLw8cUis/eJquWv7vrmlnshXBXLOrDekNq/mGhdpUmguDNEGX/3yT+8QYRv +yeCqLVWcfkQ7KkXAeet0tVPNGQQF0+yS80Hv2/LBcskhL467qa79Xm+QPbBbhsEB +aa4rettMLEdxk3IB1dgXdWhdJ4zBD+RFjczJbQlZRfmPb8sR20V/xp3x9i+SLqKp +seVoNF+LhLhEwJdMF23t2KpuiOShzC60ApjALN6/O2/XGCl0KQ+NzucX+wpirS6z +d2XfEYpsUaUFEFraOwfGXxLmluRtS6Q3+0+NPgwVQuH7EE7KuoTDUoSrUG4OFjaq +CeUeZv1IVf0sYqZQVRiMxxdoFBKUSgcaR1gzzLZgHeoZCGP0PewmZDfJMQ5rWe0D +zYYIKXUg8+oytHsz+5pQ277psXsl7iApZu56s6w3rD45w/zBeEyBhyL5JMBP8Y6y +7ReaUGsoFu3WEvrMcOsN+0Vag/SdQsvEH0PGA/ltlrlhaHKq+4t/ZwP6WxUmnaVV +JNtTWB8IqxtO0zbwK1owxjrO7t42K2isSryg/y2sQb4wgokoOzg1PqEaM8PIUvjl +qkGhwrOz4lNNQ9b6Hgy81DpnXnJkRNY7B5yKi62TCc6K/DHrFs0fHKb9Qxac5KKf +paasGWuEC5IP0lUyn81BmAVlfByBvnGmYiDmmGXLmfsyqtGFL9fpOl1Txq3/URfT +f705lzeUt9r2BT5FJtV5lkTntRzjpi5QeRiJsvfXA7nCPZj2hoLWgIm/D/HRgfVR +PIX1M7nxefRgES+T6UJNsBbGjSTgEVIPqVnyWs0JUyg4+KQ5VMU8g8SGA0dtnJyF +9JrZHy2OA/AYt/c96vJj4WdFvqw3kodIKOipBbKjBBGokaOTsLADFEYgOr51BfvO +QmxGZoXsRpD4sBOAwW039Ka5uCfuBETa+XQPtlHailaRZLlK9cZaDlzQr/K9jAgM +qOmZIKr3L8YPK3mQV+mWVYchPXTf+UyTFiWIt30z1JlyrTw1H+h62pV9f1QXDB6P +FIlfWHUK2mohWqzBnv4zFRBTVUnUDC9ONT+cVLh0cvlbRt2yy2ZgR4+d6IGH6mRH +VLgWAFpS3KS1/4NfwWRBaMvIBfqfXCzXSqVJsq7RlBSW/EBwe9TDXhcTzOLHjx4E +vdp+hqyXT62cTd7oWe78BBw3xOgpQwQ8bUdhye0kXMLNpU9j70pA7CjLVoVsdzH6 +n1EG7Mz/5NmXLy7LP8RuVU90mNQzNu8PFWtfjZ/jr3/OxoOc0Wx6mFykXkZbxKXI +xOlaOnUHKnEmsCLnZUkIxEqwKo+RYWBRtKxYsS8x8TLXyFGEfHidI75ulZM7eAS8 +jWtVNKbPIyal+nQMpqa/lKW6fiGGUVp0u2x3Pnd8luRCs2htBmXSB7W7mJ2SMCui +-----END RSA PRIVATE KEY----- diff --git a/authority/testdata/scep/root.crt b/authority/testdata/scep/root.crt new file mode 100644 index 00000000..58f9820d --- /dev/null +++ b/authority/testdata/scep/root.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBczCCARigAwIBAgIRAImbSwfqrrI6p72t0b9f6l4wCgYIKoZIzj0EAwIwFzEV +MBMGA1UEAxMMc2NlcHRlc3Ryb290MB4XDTIxMDUwNzE1MjEzMFoXDTMxMDUwNTE1 +MjEzMFowFzEVMBMGA1UEAxMMc2NlcHRlc3Ryb290MFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAE3fyAgJsDICrnXhhoxHKmXMHLoW0EM9bYiBmx1xRyol0Qa3SZMW43 +rtTykqVP3HUA3rIrLdX106s9IFcA3eIYiaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFKJr1p5QRfkHzewG3YEhPAtvFQNr +MAoGCCqGSM49BAMCA0kAMEYCIQDlXU695zKmSSfVPaPbM2cx7OlKr2n6NSyifatH +9zDITwIhAJUbbHzRJVgscxx+VSMqC2TkFvug6ryNu6kQIKNRwolr +-----END CERTIFICATE----- diff --git a/authority/testdata/scep/root.key b/authority/testdata/scep/root.key new file mode 100644 index 00000000..7dd4582b --- /dev/null +++ b/authority/testdata/scep/root.key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,0ea78864d21de199d3a737e4337589c2 + +ZD3ggzw3eDYJp8NovTWgTxk6MagLutgU2UfwbYliAl7wKvVyzwkPytwRkyAXPBM6 +jMfiAdq6wY2wEpc8OSfrvAXrGuYqlCakDhdMaFDPcS3K29VLl4BaO2X2Rfk55nBd +ASBNREKVb+hg2HV22DO7r6t+EYXTSD6iO7EB90bvKdE= +-----END EC PRIVATE KEY-----