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

Generate RFC 5280 conformant X.509 v2 CRL #1396

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
13 changes: 12 additions & 1 deletion api/crl/crl.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package crl
import (
"crypto"
"crypto/x509"
"fmt"
"math/big"
"net/http"
"os"
"time"
Expand Down Expand Up @@ -84,7 +86,16 @@ func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error {
}
}

result, err := crl.NewCRLFromDB(certs, h.ca, h.key, newExpiryTime)
number := new(big.Int)
numberString := r.URL.Query().Get("crl-number")
if numberString != "" {
log.Infof("requested CRL number of %s", numberString)
if _, err = fmt.Sscan(numberString, number); err != nil {
return err
}
}

result, err := crl.NewCRLFromDB(certs, h.ca, h.key, newExpiryTime, number)
if err != nil {
return err
}
Expand Down
79 changes: 67 additions & 12 deletions api/crl/crl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"encoding/base64"
"encoding/json"
"io"
"math/big"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

Expand Down Expand Up @@ -45,19 +47,23 @@ func prepDB() (certdb.Accessor, error) {
return dbAccessor, nil
}

func testGetCRL(t *testing.T, dbAccessor certdb.Accessor, expiry string) (resp *http.Response, body []byte) {
func testGetCRL(t *testing.T, dbAccessor certdb.Accessor, expiry, number string) (resp *http.Response, body []byte) {
handler, err := NewHandler(dbAccessor, testCaFile, testCaKeyFile)
if err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(handler)
defer ts.Close()

query := url.Values{}
if expiry != "" {
resp, err = http.Get(ts.URL + "?expiry=" + expiry)
} else {
resp, err = http.Get(ts.URL)
query.Set("expiry", expiry)
}
if number != "" {
query.Set("crl-number", number)
}

resp, err = http.Get(ts.URL + "?" + query.Encode())
if err != nil {
t.Fatal(err)
}
Expand All @@ -74,7 +80,7 @@ func TestCRLGeneration(t *testing.T) {
t.Fatal(err)
}

resp, body := testGetCRL(t, dbAccessor, "")
resp, body := testGetCRL(t, dbAccessor, "", "")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
Expand All @@ -89,14 +95,14 @@ func TestCRLGeneration(t *testing.T) {
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
parsedCrl, err := x509.ParseRevocationList(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
if parsedCrl.NextUpdate.Before(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request will expire after 5 days, this shouldn't happen")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
certs := parsedCrl.RevokedCertificateEntries
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
Expand All @@ -106,6 +112,10 @@ func TestCRLGeneration(t *testing.T) {
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}

if big.NewInt(0).Cmp(parsedCrl.Number) != 0 {
t.Fatalf("CRL number was incorrect: %s, expect: 0", parsedCrl.Number)
}
}

func TestCRLGenerationWithExpiry(t *testing.T) {
Expand All @@ -114,7 +124,7 @@ func TestCRLGenerationWithExpiry(t *testing.T) {
t.Fatal(err)
}

resp, body := testGetCRL(t, dbAccessor, "119h")
resp, body := testGetCRL(t, dbAccessor, "119h", "")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
Expand All @@ -129,14 +139,14 @@ func TestCRLGenerationWithExpiry(t *testing.T) {
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseCRL(crlBytesDER)
parsedCrl, err := x509.ParseRevocationList(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
if !parsedCrl.HasExpired(time.Now().Add(5 * helpers.OneDay)) {
if !parsedCrl.NextUpdate.Before(time.Now().Add(5 * helpers.OneDay)) {
t.Fatal("the request should have expired")
}
certs := parsedCrl.TBSCertList.RevokedCertificates
certs := parsedCrl.RevokedCertificateEntries
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}
Expand All @@ -146,4 +156,49 @@ func TestCRLGenerationWithExpiry(t *testing.T) {
if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}

if big.NewInt(0).Cmp(parsedCrl.Number) != 0 {
t.Fatalf("CRL number was incorrect: %s, expect: 0", parsedCrl.Number)
}
}

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

resp, body := testGetCRL(t, dbAccessor, "", "1")
if resp.StatusCode != http.StatusOK {
t.Fatal("unexpected HTTP status code; expected OK", string(body))
}
message := new(api.Response)
err = json.Unmarshal(body, message)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}

crlBytes := message.Result.(string)
crlBytesDER, err := base64.StdEncoding.DecodeString(crlBytes)
if err != nil {
t.Fatal("failed to decode certificate ", err)
}
parsedCrl, err := x509.ParseRevocationList(crlBytesDER)
if err != nil {
t.Fatal("failed to get certificate ", err)
}
certs := parsedCrl.RevokedCertificateEntries
if len(certs) != 1 {
t.Fatal("failed to get one certificate")
}

cert := certs[0]

if cert.SerialNumber.String() != "1" {
t.Fatal("cert was not correctly inserted in CRL, serial was ", cert.SerialNumber)
}

if big.NewInt(1).Cmp(parsedCrl.Number) != 0 {
t.Fatalf("CRL number was incorrect: %s, expect: 1", parsedCrl.Number)
}
}
16 changes: 12 additions & 4 deletions api/gencrl/gencrl.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package gencrl

import (
"crypto/rand"
"crypto/x509/pkix"
"crypto/x509"
"encoding/json"
"io"
"math/big"
Expand All @@ -24,12 +24,13 @@ type jsonCRLRequest struct {
SerialNumber []string `json:"serialNumber"`
PrivateKey string `json:"issuingKey"`
ExpiryTime string `json:"expireTime"`
CRLNumber int64 `json:"crlNumber"`
}

// Handle responds to requests for crl generation. It creates this crl
// based off of the given certificate, serial numbers, and private key
func gencrlHandler(w http.ResponseWriter, r *http.Request) error {
var revokedCerts []pkix.RevokedCertificate
var revokedCerts []x509.RevocationListEntry
var oneWeek = time.Duration(604800) * time.Second
var newExpiryTime = time.Now()

Expand Down Expand Up @@ -72,7 +73,7 @@ func gencrlHandler(w http.ResponseWriter, r *http.Request) error {
for _, value := range req.SerialNumber {
tempBigInt := new(big.Int)
tempBigInt.SetString(value, 10)
tempCert := pkix.RevokedCertificate{
tempCert := x509.RevocationListEntry{
SerialNumber: tempBigInt,
RevocationTime: time.Now(),
}
Expand All @@ -85,7 +86,14 @@ func gencrlHandler(w http.ResponseWriter, r *http.Request) error {
return errors.NewBadRequestString("malformed Private Key")
}

result, err := cert.CreateCRL(rand.Reader, key, revokedCerts, time.Now(), newExpiryTime)
tpl := &x509.RevocationList{
Issuer: cert.Subject,
RevokedCertificateEntries: revokedCerts,
NextUpdate: newExpiryTime,
ThisUpdate: time.Now(),
Number: big.NewInt(req.CRLNumber),
}
result, err := x509.CreateRevocationList(rand.Reader, tpl, cert, key)
if err != nil {
log.Debugf("unable to create CRL: %v", err)
return err
Expand Down
7 changes: 5 additions & 2 deletions api/gencrl/gencrl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
type testJSON struct {
Certificate string
SerialNumber []string
CRLNumber int64
PrivateKey string
ExpiryTime string
ExpectedHTTPStatus int
Expand All @@ -31,6 +32,7 @@ type testJSON struct {
var tester = testJSON{
Certificate: cert,
SerialNumber: []string{"1", "2", "3"},
CRLNumber: 1,
PrivateKey: key,
ExpiryTime: "2000",
ExpectedHTTPStatus: 200,
Expand All @@ -50,7 +52,7 @@ func newCRLServer(t *testing.T) *httptest.Server {
return ts
}

func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, serialList []string) (resp *http.Response, body []byte) {
func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, serialList []string, number int64) (resp *http.Response, body []byte) {
ts := newCRLServer(t)
defer ts.Close()

Expand All @@ -65,6 +67,7 @@ func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, s
}

obj["serialNumber"] = serialList
obj["crlNumber"] = number

if issuingKey != "" {
c, err := os.ReadFile(issuingKey)
Expand Down Expand Up @@ -93,7 +96,7 @@ func testCRLCreation(t *testing.T, issuingKey, certFile string, expiry string, s
}

func TestCRL(t *testing.T) {
resp, body := testCRLCreation(t, tester.PrivateKey, tester.Certificate, tester.ExpiryTime, tester.SerialNumber)
resp, body := testCRLCreation(t, tester.PrivateKey, tester.Certificate, tester.ExpiryTime, tester.SerialNumber, tester.CRLNumber)
if resp.StatusCode != tester.ExpectedHTTPStatus {
t.Logf("expected: %d, have %d", tester.ExpectedHTTPStatus, resp.StatusCode)
t.Fatal(resp.Status, tester.ExpectedHTTPStatus, string(body))
Expand Down
51 changes: 26 additions & 25 deletions api/testdata/ca.pem
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEmzCCA4OgAwIBAgIMAMSvNBgypwaaSQ5iMA0GCSqGSIb3DQEBBQUAMIGMMQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzETMBEGA1UEChMKQ0ZTU0wgVEVTVDEbMBkGA1UEAxMSQ0ZTU0wgVEVT
VCBSb290IENBMR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwHhcNMTIx
MjEyMDIxMDMxWhcNMjIxMDIxMDIxMDMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoT
CkNGU1NMIFRFU1QxGzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqG
SIb3DQEJARYPdGVzdEB0ZXN0LmxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsRp1xSfIDoD/40Bo4Hls3sFn4dav5NgxbZGpVyGF7dJI9u0eEnL4
BUGssPaUFLWC83CZxujUEiEfE0oKX+uOhhGv3+j5xSTNM764m2eSiN53cdZtK05d
hwq9uS8LtjKOQeN1mQ5qmiqxBMdjkKgMsVw5lMCgoYKo57kaKFyXzdpNVDzqw+pt
HWmuNtDQjK3qT5Ma06mYPmIGYhIZYLY7oJGg9ZEaNR0GIw4zIT5JRsNiaSb5wTLw
aa0n/4vLJyVjLJcYmJBvZWj8g+taK+C4INu/jGux+bmsC9hq14tbOaTNAn/NE0qN
8oHwcRBEqfOdEYdZkxI5NWPiKNW/Q+AeXQIDAQABo4H6MIH3MB0GA1UdDgQWBBS3
0veEuqg51fusEM4p/YuWpBPsvTCBxAYDVR0jBIG8MIG5gBS30veEuqg51fusEM4p
/YuWpBPsvaGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju
aWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAoTCkNGU1NMIFRFU1Qx
GzAZBgNVBAMTEkNGU1NMIFRFU1QgUm9vdCBDQTEeMBwGCSqGSIb3DQEJARYPdGVz
dEB0ZXN0LmxvY2FsggwAxK80GDKnBppJDmIwDwYDVR0TBAgwBgEB/wIBADANBgkq
hkiG9w0BAQUFAAOCAQEAJ7r1EZYDwed6rS0+YKHdkRGRQ5Rz6A9DIVBPXrSMAGj3
F5EF2m/GJbhpVbnNJTVlgP9DDyabOZNxzdrCr4cHMkYYnocDdgAodnkw6GZ/GJTc
depbVTR4TpihFNzeDEGJePrEwM1DouGswpu97jyuCYZ3z1a60+a+3C1GwWaJ7Aet
Uqm+yLTUrMISsfnDPqJdM1NeqW3jiZ4IgcqJkieCCSpag9Xuzrp9q6rjmePvlQkv
qz020JGg6VijJ+c6Tf5y0XqbAhkBTqYtVamu9gEth9utn12EhdNjTZMPKMjjgFUd
H0N6yOEuQMl4ky7RxZBM0iPyeob6i4z2LEQilgv9MQ==
MIIEujCCA6KgAwIBAgIUFDpMIUJti3veyL7YFMSm4jwuKZ4wDQYJKoZIhvcNAQEL
BQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
DA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApDRlNTTCBURVNUMRswGQYDVQQDDBJD
RlNTTCBURVNUIFJvb3QgQ0ExHjAcBgkqhkiG9w0BCQEWD3Rlc3RAdGVzdC5sb2Nh
bDAeFw0yNDA5MzAxMDM5MjJaFw0zNDA5MjgxMDM5MjJaMIGMMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzET
MBEGA1UECgwKQ0ZTU0wgVEVTVDEbMBkGA1UEAwwSQ0ZTU0wgVEVTVCBSb290IENB
MR4wHAYJKoZIhvcNAQkBFg90ZXN0QHRlc3QubG9jYWwwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCxGnXFJ8gOgP/jQGjgeWzewWfh1q/k2DFtkalXIYXt
0kj27R4ScvgFQayw9pQUtYLzcJnG6NQSIR8TSgpf646GEa/f6PnFJM0zvribZ5KI
3ndx1m0rTl2HCr25Lwu2Mo5B43WZDmqaKrEEx2OQqAyxXDmUwKChgqjnuRooXJfN
2k1UPOrD6m0daa420NCMrepPkxrTqZg+YgZiEhlgtjugkaD1kRo1HQYjDjMhPklG
w2JpJvnBMvBprSf/i8snJWMslxiYkG9laPyD61or4Lgg27+Ma7H5uawL2GrXi1s5
pM0Cf80TSo3ygfBxEESp850Rh1mTEjk1Y+Io1b9D4B5dAgMBAAGjggEQMIIBDDAd
BgNVHQ4EFgQUt9L3hLqoOdX7rBDOKf2LlqQT7L0wgcwGA1UdIwSBxDCBwYAUt9L3
hLqoOdX7rBDOKf2LlqQT7L2hgZKkgY8wgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
DApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApD
RlNTTCBURVNUMRswGQYDVQQDDBJDRlNTTCBURVNUIFJvb3QgQ0ExHjAcBgkqhkiG
9w0BCQEWD3Rlc3RAdGVzdC5sb2NhbIIUFDpMIUJti3veyL7YFMSm4jwuKZ4wDwYD
VR0TBAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBABD5
9TNUUuNQYy6V5vY1OCLBDieyWUmPxwKu7B8QAs9yGK/z/r8Vs33NeJPV26A0zHK5
x9RuNBK938GABEaEZWfhOw2oaouEbd7NSgoB8cIFa0ki1+uSbSaNilXCkC6SCbab
8L0CZX7AtVE2wX0mulOCrLDqZpLWh7voGuQKlmbz1oCLDcD+RJATtUQK7Wpb0MKf
s/h0YMKVTmb6CcHVt/VgWS2ODGD2Wyf/MaHbpsCsZreqLceLHty9J2+yh1hkWc7O
MK6svsOFcAG5j4QS0xxMQPJdXn3Yp7qZQjIwPBbptBqDL45jqYauPGdBhDEwKVVu
D6PWBZUHDjYhMtSRDEg=
-----END CERTIFICATE-----
Binary file modified certdb/testdb/certstore_development.db
Binary file not shown.
2 changes: 2 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Config struct {
AKI string
DBConfigFile string
CRLExpiration time.Duration
CRLNumber int64
Disable string
}

Expand Down Expand Up @@ -130,6 +131,7 @@ func registerFlags(c *Config, f *flag.FlagSet) {
f.StringVar(&c.AKI, "aki", "", "certificate issuer (authority) key identifier")
f.StringVar(&c.DBConfigFile, "db-config", "", "certificate db configuration file")
f.DurationVar(&c.CRLExpiration, "expiry", 7*helpers.OneDay, "time from now after which the CRL will expire (default: one week)")
f.Int64Var(&c.CRLNumber, "crl-number", 0, "CRL number")
f.IntVar(&log.Level, "loglevel", log.LevelInfo, "Log level (0 = DEBUG, 5 = FATAL)")
f.StringVar(&c.Disable, "disable", "", "endpoints to disable")
}
Expand Down
3 changes: 2 additions & 1 deletion cli/crl/crl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package crl

import (
"math/big"
"os"

"github.com/cloudflare/cfssl/certdb/dbconf"
Expand Down Expand Up @@ -83,7 +84,7 @@ func generateCRL(c cli.Config) (crlBytes []byte, err error) {
return nil, err
}

req, err := crl.NewCRLFromDB(certs, issuerCert, key, c.CRLExpiration)
req, err := crl.NewCRLFromDB(certs, issuerCert, key, c.CRLExpiration, big.NewInt(c.CRLNumber))
if err != nil {
return nil, err
}
Expand Down
Loading