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 +}