From 45ffe16b32d600c3040b74fb3ac22e954cc23a6f Mon Sep 17 00:00:00 2001 From: Archangel_SDY Date: Mon, 30 Sep 2024 17:29:20 +0800 Subject: [PATCH] Generate RFC 5280 conformant X.509 v2 CRL * Replace `CreateCRL` with `CreateRevocationList`. * Replace `ParseCRL` with `ParseRevocationList`. * Replace `ca.pem` in testdata with a new one that includes `cRLSign` key usage. * Add an optional parameter to specify CRL number in `crl` and `gencrl`. --- api/crl/crl.go | 13 +++- api/crl/crl_test.go | 79 +++++++++++++++++++++---- api/gencrl/gencrl.go | 16 +++-- api/gencrl/gencrl_test.go | 7 ++- api/testdata/ca.pem | 51 ++++++++-------- certdb/testdb/certstore_development.db | Bin 18432 -> 18432 bytes cli/config.go | 2 + cli/crl/crl.go | 3 +- cli/crl/crl_test.go | 35 ++++++++--- cli/gencrl/gencrl.go | 8 ++- cli/gencrl/gencrl_test.go | 7 +++ crl/crl.go | 29 +++++---- crl/crl_test.go | 21 +++++-- 13 files changed, 198 insertions(+), 73 deletions(-) diff --git a/api/crl/crl.go b/api/crl/crl.go index c2525c131..423a33895 100644 --- a/api/crl/crl.go +++ b/api/crl/crl.go @@ -4,6 +4,8 @@ package crl import ( "crypto" "crypto/x509" + "fmt" + "math/big" "net/http" "os" "time" @@ -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 } diff --git a/api/crl/crl_test.go b/api/crl/crl_test.go index f01609e71..9a8e622f0 100644 --- a/api/crl/crl_test.go +++ b/api/crl/crl_test.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "encoding/json" "io" + "math/big" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -45,7 +47,7 @@ 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) @@ -53,11 +55,15 @@ func testGetCRL(t *testing.T, dbAccessor certdb.Accessor, expiry string) (resp * 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) } @@ -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)) } @@ -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") } @@ -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) { @@ -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)) } @@ -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") } @@ -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) + } } diff --git a/api/gencrl/gencrl.go b/api/gencrl/gencrl.go index 6cfa5e63f..cfae97ae9 100644 --- a/api/gencrl/gencrl.go +++ b/api/gencrl/gencrl.go @@ -3,7 +3,7 @@ package gencrl import ( "crypto/rand" - "crypto/x509/pkix" + "crypto/x509" "encoding/json" "io" "math/big" @@ -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() @@ -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(), } @@ -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 diff --git a/api/gencrl/gencrl_test.go b/api/gencrl/gencrl_test.go index dd1c8f266..a3706b946 100644 --- a/api/gencrl/gencrl_test.go +++ b/api/gencrl/gencrl_test.go @@ -22,6 +22,7 @@ const ( type testJSON struct { Certificate string SerialNumber []string + CRLNumber int64 PrivateKey string ExpiryTime string ExpectedHTTPStatus int @@ -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, @@ -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() @@ -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) @@ -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)) diff --git a/api/testdata/ca.pem b/api/testdata/ca.pem index 1a1f5a93b..67aa1745f 100644 --- a/api/testdata/ca.pem +++ b/api/testdata/ca.pem @@ -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----- diff --git a/certdb/testdb/certstore_development.db b/certdb/testdb/certstore_development.db index a4d26505a17cce2aa3de911fc2f46d66dfc73557..39559bcccdd54b9195228d48303c0e8e04ad6492 100644 GIT binary patch delta 220 zcmZpez}PT>ae@>dXDae@>d#}@_$1|cBk0b-7c8fI)j5j~BpjVX(`xh63%Phxt@{G0jPW;ARb-8`1y~4lEb| diff --git a/cli/config.go b/cli/config.go index 219bd1812..6229ed1d6 100644 --- a/cli/config.go +++ b/cli/config.go @@ -69,6 +69,7 @@ type Config struct { AKI string DBConfigFile string CRLExpiration time.Duration + CRLNumber int64 Disable string } @@ -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") } diff --git a/cli/crl/crl.go b/cli/crl/crl.go index 002a92845..33a810372 100644 --- a/cli/crl/crl.go +++ b/cli/crl/crl.go @@ -2,6 +2,7 @@ package crl import ( + "math/big" "os" "github.com/cloudflare/cfssl/certdb/dbconf" @@ -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 } diff --git a/cli/crl/crl_test.go b/cli/crl/crl_test.go index bcbec98b5..7457e0a6f 100644 --- a/cli/crl/crl_test.go +++ b/cli/crl/crl_test.go @@ -2,6 +2,7 @@ package crl import ( "crypto/x509" + "math/big" "testing" "time" @@ -42,15 +43,15 @@ func prepDB() (err error) { return } -func verifyCRL(t *testing.T, crlBytesDER []byte, serial string, expireAfter time.Duration) { - parsedCrl, err := x509.ParseCRL(crlBytesDER) +func verifyCRL(t *testing.T, crlBytesDER []byte, serial string, expireAfter time.Duration, number *big.Int) { + parsedCrl, err := x509.ParseRevocationList(crlBytesDER) if err != nil { t.Fatal("failed to get certificate ", err) } - if !parsedCrl.HasExpired(time.Now().Add(expireAfter)) { - t.Fatal("the CRL should have expired") + if !parsedCrl.NextUpdate.Before(time.Now().Add(expireAfter)) { + t.Fatalf("the CRL should have expired") } - certs := parsedCrl.TBSCertList.RevokedCertificates + certs := parsedCrl.RevokedCertificateEntries if len(certs) != 1 { t.Fatal("failed to get one certificate") } @@ -60,6 +61,10 @@ func verifyCRL(t *testing.T, crlBytesDER []byte, serial string, expireAfter time if cert.SerialNumber.String() != serial { t.Fatal("cert was not correctly inserted in CRL, serial was " + cert.SerialNumber.String()) } + + if number.Cmp(parsedCrl.Number) != 0 { + t.Fatalf("CRL number was incorrect: %s, expect: %s", parsedCrl.Number, number) + } } func TestRevokeMain(t *testing.T) { @@ -68,12 +73,12 @@ func TestRevokeMain(t *testing.T) { t.Fatal(err) } - crlBytes, err := generateCRL(cli.Config{CAFile: testCaFile, CAKeyFile: testCaKeyFile, DBConfigFile: "../testdata/db-config.json"}) + crlBytes, err := generateCRL(cli.Config{CAFile: testCaFile, CAKeyFile: testCaKeyFile, DBConfigFile: "../testdata/db-config.json", CRLExpiration: time.Minute}) if err != nil { t.Fatal(err) } - verifyCRL(t, crlBytes, "1", 7*helpers.OneDay+time.Second) + verifyCRL(t, crlBytes, "1", 7*helpers.OneDay+time.Second, big.NewInt(0)) } func TestRevokeExpiry(t *testing.T) { @@ -87,5 +92,19 @@ func TestRevokeExpiry(t *testing.T) { t.Fatal(err) } - verifyCRL(t, crlBytes, "1", 23*time.Hour+time.Second) + verifyCRL(t, crlBytes, "1", 23*time.Hour+time.Second, big.NewInt(0)) +} + +func TestRevokeNumber(t *testing.T) { + err := prepDB() + if err != nil { + t.Fatal(err) + } + + crlBytes, err := generateCRL(cli.Config{CAFile: testCaFile, CAKeyFile: testCaKeyFile, DBConfigFile: "../testdata/db-config.json", CRLExpiration: time.Minute, CRLNumber: 1}) + if err != nil { + t.Fatal(err) + } + + verifyCRL(t, crlBytes, "1", 23*time.Hour+time.Second, big.NewInt(1)) } diff --git a/cli/gencrl/gencrl.go b/cli/gencrl/gencrl.go index b1d0e786a..46d457c4f 100644 --- a/cli/gencrl/gencrl.go +++ b/cli/gencrl/gencrl.go @@ -2,9 +2,11 @@ package gencrl import ( + "math/big" + "strings" + "github.com/cloudflare/cfssl/cli" "github.com/cloudflare/cfssl/crl" - "strings" ) var gencrlUsageText = `cfssl gencrl -- generate a new Certificate Revocation List @@ -20,7 +22,7 @@ Arguments: Flags: ` -var gencrlFlags = []string{} +var gencrlFlags = []string{"crl-number"} func gencrlMain(args []string, c cli.Config) (err error) { serialList, args, err := cli.PopFirstArgument(args) @@ -69,7 +71,7 @@ func gencrlMain(args []string, c cli.Config) (err error) { } - req, err := crl.NewCRLFromFile(serialListBytes, certFileBytes, keyBytes, timeString) + req, err := crl.NewCRLFromFile(serialListBytes, certFileBytes, keyBytes, timeString, big.NewInt(int64(c.CRLNumber))) if err != nil { return } diff --git a/cli/gencrl/gencrl_test.go b/cli/gencrl/gencrl_test.go index 9077ecb8b..f4a11769b 100644 --- a/cli/gencrl/gencrl_test.go +++ b/cli/gencrl/gencrl_test.go @@ -23,3 +23,10 @@ func TestGencrlTime(t *testing.T) { t.Fatal(err) } } + +func TestGencrlTimeNumber(t *testing.T) { + err := gencrlMain([]string{"testdata/serialList", "testdata/caTwo.pem", "testdata/ca-keyTwo.pem", "123"}, cli.Config{CRLNumber: 1}) + if err != nil { + t.Fatal(err) + } +} diff --git a/crl/crl.go b/crl/crl.go index bbe29a503..2ac0d0cad 100644 --- a/crl/crl.go +++ b/crl/crl.go @@ -5,7 +5,6 @@ import ( "crypto" "crypto/rand" "crypto/x509" - "crypto/x509/pkix" "math/big" "os" "strconv" @@ -19,9 +18,9 @@ import ( // NewCRLFromFile takes in a list of serial numbers, one per line, as well as the issuing certificate // of the CRL, and the private key. This function is then used to parse the list and generate a CRL -func NewCRLFromFile(serialList, issuerFile, keyFile []byte, expiryTime string) ([]byte, error) { +func NewCRLFromFile(serialList, issuerFile, keyFile []byte, expiryTime string, number *big.Int) ([]byte, error) { - var revokedCerts []pkix.RevokedCertificate + var revokedCerts []x509.RevocationListEntry var oneWeek = time.Duration(604800) * time.Second expiryInt, err := strconv.ParseInt(expiryTime, 0, 32) @@ -51,7 +50,7 @@ func NewCRLFromFile(serialList, issuerFile, keyFile []byte, expiryTime string) ( tempBigInt := new(big.Int) tempBigInt.SetString(value, 10) - tempCert := pkix.RevokedCertificate{ + tempCert := x509.RevocationListEntry{ SerialNumber: tempBigInt, RevocationTime: time.Now(), } @@ -71,13 +70,13 @@ func NewCRLFromFile(serialList, issuerFile, keyFile []byte, expiryTime string) ( return nil, err } - return CreateGenericCRL(revokedCerts, key, issuerCert, newExpiryTime) + return CreateGenericCRL(revokedCerts, key, issuerCert, newExpiryTime, number) } // NewCRLFromDB takes in a list of CertificateRecords, as well as the issuing certificate // of the CRL, and the private key. This function is then used to parse the records and generate a CRL -func NewCRLFromDB(certs []certdb.CertificateRecord, issuerCert *x509.Certificate, key crypto.Signer, expiryTime time.Duration) ([]byte, error) { - var revokedCerts []pkix.RevokedCertificate +func NewCRLFromDB(certs []certdb.CertificateRecord, issuerCert *x509.Certificate, key crypto.Signer, expiryTime time.Duration, number *big.Int) ([]byte, error) { + var revokedCerts []x509.RevocationListEntry newExpiryTime := time.Now().Add(expiryTime) @@ -85,20 +84,28 @@ func NewCRLFromDB(certs []certdb.CertificateRecord, issuerCert *x509.Certificate for _, certRecord := range certs { serialInt := new(big.Int) serialInt.SetString(certRecord.Serial, 10) - tempCert := pkix.RevokedCertificate{ + tempCert := x509.RevocationListEntry{ SerialNumber: serialInt, RevocationTime: certRecord.RevokedAt, } revokedCerts = append(revokedCerts, tempCert) } - return CreateGenericCRL(revokedCerts, key, issuerCert, newExpiryTime) + return CreateGenericCRL(revokedCerts, key, issuerCert, newExpiryTime, number) } // CreateGenericCRL is a helper function that takes in all of the information above, and then calls the createCRL // function. This outputs the bytes of the created CRL. -func CreateGenericCRL(certList []pkix.RevokedCertificate, key crypto.Signer, issuingCert *x509.Certificate, expiryTime time.Time) ([]byte, error) { - crlBytes, err := issuingCert.CreateCRL(rand.Reader, key, certList, time.Now(), expiryTime) +func CreateGenericCRL(certList []x509.RevocationListEntry, key crypto.Signer, issuingCert *x509.Certificate, expiryTime time.Time, number *big.Int) ([]byte, error) { + tpl := &x509.RevocationList{ + Issuer: issuingCert.Subject, + RevokedCertificateEntries: certList, + NextUpdate: expiryTime, + ThisUpdate: time.Now(), + Number: number, + } + + crlBytes, err := x509.CreateRevocationList(rand.Reader, tpl, issuingCert, key) if err != nil { log.Debugf("error creating CRL: %s", err) } diff --git a/crl/crl_test.go b/crl/crl_test.go index c1fdbf1fa..755da4284 100644 --- a/crl/crl_test.go +++ b/crl/crl_test.go @@ -2,6 +2,7 @@ package crl import ( "crypto/x509" + "math/big" "os" "testing" ) @@ -31,21 +32,25 @@ func TestNewCRLFromFile(t *testing.T) { t.Fatal(err) } - crl, err := NewCRLFromFile(serialListBytes, tryTwoCertBytes, tryTwoKeyBytes, "0") + crl, err := NewCRLFromFile(serialListBytes, tryTwoCertBytes, tryTwoKeyBytes, "0", big.NewInt(1)) if err != nil { t.Fatal(err) } - certList, err := x509.ParseDERCRL(crl) + certList, err := x509.ParseRevocationList(crl) if err != nil { t.Fatal(err) } - numCerts := len(certList.TBSCertList.RevokedCertificates) + numCerts := len(certList.RevokedCertificateEntries) expectedNum := 4 if expectedNum != numCerts { t.Fatal("Wrong number of expired certificates") } + + if big.NewInt(1).Cmp(certList.Number) != 0 { + t.Fatal("Wrong CRL number") + } } func TestNewCRLFromFileWithoutRevocations(t *testing.T) { @@ -59,19 +64,23 @@ func TestNewCRLFromFileWithoutRevocations(t *testing.T) { t.Fatal(err) } - crl, err := NewCRLFromFile([]byte("\n \n"), tryTwoCertBytes, tryTwoKeyBytes, "0") + crl, err := NewCRLFromFile([]byte("\n \n"), tryTwoCertBytes, tryTwoKeyBytes, "0", big.NewInt(0)) if err != nil { t.Fatal(err) } - certList, err := x509.ParseDERCRL(crl) + certList, err := x509.ParseRevocationList(crl) if err != nil { t.Fatal(err) } - numCerts := len(certList.TBSCertList.RevokedCertificates) + numCerts := len(certList.RevokedCertificateEntries) expectedNum := 0 if expectedNum != numCerts { t.Fatal("Wrong number of expired certificates") } + + if big.NewInt(0).Cmp(certList.Number) != 0 { + t.Fatal("Wrong CRL number") + } }