Skip to content

Commit

Permalink
Merge pull request #1181 from myokoyama28/fix-certadd
Browse files Browse the repository at this point in the history
  • Loading branch information
nickysemenza authored Mar 26, 2021
2 parents b1512a1 + ea20c62 commit 0707dc7
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 34 deletions.
54 changes: 37 additions & 17 deletions api/certadd/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package certadd

import (
"bytes"
"database/sql"
"encoding/hex"
"encoding/json"
"io/ioutil"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/cloudflare/cfssl/errors"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/ocsp"
"github.com/jmoiron/sqlx/types"

"encoding/base64"

Expand Down Expand Up @@ -48,14 +50,19 @@ func NewHandler(dbAccessor certdb.Accessor, signer ocsp.Signer) http.Handler {
// AddRequest describes a request from a client to insert a
// certificate into the database.
type AddRequest struct {
Serial string `json:"serial_number"`
AKI string `json:"authority_key_identifier"`
CALabel string `json:"ca_label"`
Status string `json:"status"`
Reason int `json:"reason"`
Expiry time.Time `json:"expiry"`
RevokedAt time.Time `json:"revoked_at"`
PEM string `json:"pem"`
Serial string `json:"serial_number"`
AKI string `json:"authority_key_identifier"`
CALabel string `json:"ca_label"`
Status string `json:"status"`
Reason int `json:"reason"`
Expiry time.Time `json:"expiry"`
RevokedAt time.Time `json:"revoked_at"`
PEM string `json:"pem"`
IssuedAt *time.Time `json:"issued_at"`
NotBefore *time.Time `json:"not_before"`
MetadataJSON types.JSONText `json:"metadata"`
SansJSON types.JSONText `json:"sans"`
CommonName string `json:"common_name"`
}

// Map of valid reason codes
Expand Down Expand Up @@ -113,14 +120,18 @@ func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
return errors.NewBadRequestString("The provided certificate is empty")
}

if req.Expiry.IsZero() {
return errors.NewBadRequestString("Expiry is required but not provided")
}

// Parse the certificate and validate that it matches
cert, err := helpers.ParseCertificatePEM([]byte(req.PEM))
if err != nil {
return errors.NewBadRequestString("Unable to parse PEM encoded certificates")
}

serialBigInt := new(big.Int)
if _, success := serialBigInt.SetString(req.Serial, 16); !success {
if _, success := serialBigInt.SetString(req.Serial, 10); !success {
return errors.NewBadRequestString("Unable to parse serial key of request")
}

Expand All @@ -137,15 +148,24 @@ func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
return errors.NewBadRequestString("Authority key identifier of request and certificate do not match")
}

if req.Expiry != cert.NotAfter {
return errors.NewBadRequestString("Expiry of request and certificate do not match")
}

cr := certdb.CertificateRecord{
Serial: req.Serial,
AKI: req.AKI,
CALabel: req.CALabel,
Status: req.Status,
Reason: req.Reason,
Expiry: req.Expiry,
RevokedAt: req.RevokedAt,
PEM: req.PEM,
Serial: req.Serial,
AKI: req.AKI,
CALabel: req.CALabel,
Status: req.Status,
Reason: req.Reason,
Expiry: req.Expiry,
RevokedAt: req.RevokedAt,
PEM: req.PEM,
IssuedAt: req.IssuedAt,
NotBefore: req.NotBefore,
MetadataJSON: req.MetadataJSON,
SANsJSON: req.SansJSON,
CommonName: sql.NullString{String: req.CommonName, Valid: req.CommonName != ""},
}

err = h.dbAccessor.InsertCertificate(cr)
Expand Down
121 changes: 104 additions & 17 deletions api/certadd/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func makeCertificate() (serialNumber *big.Int, cert *x509.Certificate, pemBytes
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
NotAfter: time.Now(),
}
cert = &template

Expand All @@ -91,9 +92,9 @@ func makeCertificate() (serialNumber *big.Int, cert *x509.Certificate, pemBytes
Subject: pkix.Name{
Organization: []string{"Cornell CS 5152"},
},
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
AuthorityKeyId: []byte{42, 42, 42, 42},
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
IsCA: true,
BasicConstraintsValid: true,
}
issuerBytes, err := x509.CreateCertificate(rand.Reader, &issuerTemplate, &issuerTemplate, &privKey.PublicKey, privKey)
Expand Down Expand Up @@ -153,10 +154,11 @@ func TestInsertValidCertificate(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusOK {
Expand All @@ -179,7 +181,7 @@ func TestInsertValidCertificate(t *testing.T) {
t.Fatal("Could not parse returned OCSP response", err)
}

ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(10), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -223,6 +225,7 @@ func TestInsertMissingSerial(t *testing.T) {
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -236,16 +239,41 @@ func TestInsertMissingAKI(t *testing.T) {
t.Fatal(err)
}

serialNumber, _, pemBytes, signer, err := makeCertificate()
serialNumber, cert, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}

func TestInsertMissingExpiry(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}

serialNumber, cert, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -266,9 +294,10 @@ func TestInsertMissingPEM(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -293,6 +322,7 @@ func TestInsertInvalidSerial(t *testing.T) {
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -306,17 +336,18 @@ func TestInsertInvalidAKI(t *testing.T) {
t.Fatal(err)
}

serialNumber, _, pemBytes, signer, err := makeCertificate()
serialNumber, cert, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": "this is not an AKI",
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -337,10 +368,11 @@ func TestInsertInvalidStatus(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "invalid",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -361,10 +393,36 @@ func TestInsertInvalidPEM(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": "this is not a PEM certificate",
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request, got", resp.StatusCode, string(body))
}
}

func TestInsertInvalidExpiry(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}

serialNumber, cert, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
"expiry": "this is not an expiry",
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -385,10 +443,11 @@ func TestInsertWrongSerial(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": big.NewInt(1).Text(16),
"serial_number": big.NewInt(1).Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -402,17 +461,43 @@ func TestInsertWrongAKI(t *testing.T) {
t.Fatal(err)
}

serialNumber, cert, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString([]byte{7, 7}),
"status": "good",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
t.Fatal("Expected HTTP Bad Request", resp.StatusCode, string(body))
}
}

func TestInsertWrongExpiry(t *testing.T) {
dbAccessor, err := prepDB()
if err != nil {
t.Fatal(err)
}

serialNumber, _, pemBytes, signer, err := makeCertificate()

if err != nil {
t.Fatal(err)
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString([]byte{7, 7}),
"status": "good",
"pem": string(pemBytes),
"expiry": time.Now().UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusBadRequest {
Expand All @@ -433,18 +518,19 @@ func TestInsertRevokedCertificate(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
"revoked_at": time.Now(),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
})

if resp.StatusCode != http.StatusOK {
t.Fatal("Expected HTTP OK", resp.StatusCode, string(body))
}

ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(16), hex.EncodeToString(cert.AuthorityKeyId))
ocsps, err := dbAccessor.GetOCSP(serialNumber.Text(10), hex.EncodeToString(cert.AuthorityKeyId))
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -477,10 +563,11 @@ func TestInsertRevokedCertificateWithoutTime(t *testing.T) {
}

resp, body := makeRequest(t, dbAccessor, signer, map[string]interface{}{
"serial_number": serialNumber.Text(16),
"serial_number": serialNumber.Text(10),
"authority_key_identifier": hex.EncodeToString(cert.AuthorityKeyId),
"status": "revoked",
"pem": string(pemBytes),
"expiry": cert.NotAfter.UTC().Format(time.RFC3339),
// Omit RevokedAt
})

Expand Down
5 changes: 5 additions & 0 deletions cli/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
rice "github.com/GeertJohan/go.rice"
"github.com/cloudflare/cfssl/api"
"github.com/cloudflare/cfssl/api/bundle"
"github.com/cloudflare/cfssl/api/certadd"
"github.com/cloudflare/cfssl/api/certinfo"
"github.com/cloudflare/cfssl/api/crl"
"github.com/cloudflare/cfssl/api/gencrl"
Expand Down Expand Up @@ -251,6 +252,10 @@ var endpoints = map[string]func() (http.Handler, error){
"health": func() (http.Handler, error) {
return health.NewHealthCheck(), nil
},

"certadd": func() (http.Handler, error) {
return certadd.NewHandler(certsql.NewAccessor(db), nil), nil
},
}

// registerHandlers instantiates various handlers and associate them to corresponding endpoints.
Expand Down
Loading

0 comments on commit 0707dc7

Please sign in to comment.