Skip to content

Commit

Permalink
Store new certificates in database
Browse files Browse the repository at this point in the history
  • Loading branch information
hslatman committed Mar 10, 2021
1 parent 47c2a82 commit cc1ecb9
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 71 deletions.
8 changes: 2 additions & 6 deletions scep/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
96 changes: 31 additions & 65 deletions scep/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,21 @@ 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
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

GetLinkExplicit(provName string, absoluteLink bool, baseURL *url.URL, inputs ...string) string
}

// Authority is the layer that handles all SCEP interactions.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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
}

Expand All @@ -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,
},
Expand Down
80 changes: 80 additions & 0 deletions scep/certificate.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit cc1ecb9

Please sign in to comment.