Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make certadd API available #1181

Merged
merged 3 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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