Skip to content

Commit bf64611

Browse files
authored
feat: add crl support (#13)
Create an empty CRL during CA creation
1 parent 74c5bbe commit bf64611

11 files changed

+181
-17
lines changed

certify.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
// Certificate hold certificate information
1919
type Certificate struct {
20+
SerialNumber *big.Int
2021
Subject pkix.Name
2122
NotBefore time.Time
2223
NotAfter time.Time
@@ -25,12 +26,15 @@ type Certificate struct {
2526
IsCA bool
2627
Parent *x509.Certificate
2728
ParentPrivateKey interface{}
29+
KeyUsage x509.KeyUsage
2830
ExtentedKeyUsage []x509.ExtKeyUsage
31+
SubjectKeyId []byte
2932
}
3033

3134
// Result hold created certificate in []byte format
3235
type Result struct {
33-
Certificate []byte
36+
ByteCert []byte
37+
Cert *x509.Certificate
3438
}
3539

3640
// GetSerial returns serial and an error
@@ -44,17 +48,19 @@ func GetSerial() (*big.Int, error) {
4448
}
4549

4650
// SetTemplate set template for x509.Certificate from given Certificate struct
47-
func (c *Certificate) SetTemplate(serial *big.Int) x509.Certificate {
51+
func (c *Certificate) SetTemplate() x509.Certificate {
4852
return x509.Certificate{
49-
SerialNumber: serial,
53+
SerialNumber: c.SerialNumber,
5054
Subject: c.Subject,
5155
NotBefore: c.NotBefore,
5256
NotAfter: c.NotAfter,
5357
ExtKeyUsage: c.ExtentedKeyUsage,
58+
KeyUsage: c.KeyUsage,
5459
IsCA: c.IsCA,
5560
IPAddresses: c.IPAddress,
5661
DNSNames: c.DNSNames,
5762
BasicConstraintsValid: true,
63+
SubjectKeyId: c.SubjectKeyId,
5864
}
5965
}
6066

@@ -65,7 +71,8 @@ func (c *Certificate) GetCertificate(pkey *ecdsa.PrivateKey) (*Result, error) {
6571
return nil, err
6672
}
6773

68-
template := c.SetTemplate(serial)
74+
c.SerialNumber = serial
75+
template := c.SetTemplate()
6976

7077
if c.Parent == nil {
7178
c.Parent = &template
@@ -80,7 +87,7 @@ func (c *Certificate) GetCertificate(pkey *ecdsa.PrivateKey) (*Result, error) {
8087
return nil, err
8188
}
8289

83-
return &Result{Certificate: der}, nil
90+
return &Result{ByteCert: der, Cert: c.Parent}, nil
8491
}
8592

8693
// String returns certificate in string format
@@ -89,7 +96,7 @@ func (r *Result) String() string {
8996

9097
if err := pem.Encode(&w, &pem.Block{
9198
Type: "CERTIFICATE",
92-
Bytes: r.Certificate,
99+
Bytes: r.ByteCert,
93100
}); err != nil {
94101
return ""
95102
}

cmd/certify/command.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ func initCA(args []string) error {
1919
}
2020
fmt.Println("CA private key file generated", caKeyPath)
2121

22-
if err := generateCA(pkey.PrivateKey, args, caPath); err != nil {
22+
caCert, err := generateCA(pkey.PrivateKey, args, caPath)
23+
if err != nil {
2324
return err
2425
}
2526

2627
fmt.Println("CA certificate file generated", caPath)
28+
29+
if err := generateCRL(pkey.PrivateKey, caCert.Cert); err != nil {
30+
return err
31+
}
32+
fmt.Println("CRL file generated", caCRLPath)
33+
2734
return nil
2835
}
2936

cmd/certify/command_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func TestInitCA(t *testing.T) {
7272
t.Cleanup(func() {
7373
os.Remove(caPath)
7474
os.Remove(caKeyPath)
75+
os.Remove(caCRLPath)
7576
})
7677
})
7778
}
@@ -246,6 +247,7 @@ func TestCreateCertificate(t *testing.T) {
246247
t.Cleanup(func() {
247248
os.Remove(caPath)
248249
os.Remove(caKeyPath)
250+
os.Remove(caCRLPath)
249251
os.Remove("nothinux.local.pem")
250252
os.Remove("nothinux.local-key.pem")
251253
})
@@ -265,6 +267,7 @@ func TestCreateIntermediateCertificate(t *testing.T) {
265267
t.Cleanup(func() {
266268
os.Remove(caPath)
267269
os.Remove(caKeyPath)
270+
os.Remove(caCRLPath)
268271
os.Remove(caInterPath)
269272
os.Remove(caInterKeyPath)
270273
})

cmd/certify/helper.go

+24-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"crypto/ecdsa"
55
"crypto/rand"
6+
"crypto/sha1"
67
"crypto/tls"
78
"crypto/x509"
89
"crypto/x509/pkix"
@@ -28,25 +29,43 @@ func generatePrivateKey(path string) (*certify.PrivateKey, error) {
2829
return p, store(p.String(), path)
2930
}
3031

31-
func generateCA(pkey *ecdsa.PrivateKey, args []string, path string) error {
32+
func generateCA(pkey *ecdsa.PrivateKey, args []string, path string) (*certify.Result, error) {
3233
_, _, cn, org, expiry, _ := parseArgs(args)
3334

35+
b, err := x509.MarshalPKIXPublicKey(&pkey.PublicKey)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
ski := sha1.Sum(b)
41+
3442
template := certify.Certificate{
3543
Subject: pkix.Name{
3644
Organization: []string{org},
3745
CommonName: cn,
3846
},
39-
NotBefore: time.Now(),
40-
NotAfter: expiry,
41-
IsCA: true,
47+
NotBefore: time.Now(),
48+
NotAfter: expiry,
49+
IsCA: true,
50+
KeyUsage: x509.KeyUsageCRLSign,
51+
SubjectKeyId: ski[:],
4252
}
4353

4454
caCert, err := template.GetCertificate(pkey)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return caCert, store(caCert.String(), path)
60+
}
61+
62+
func generateCRL(pkey *ecdsa.PrivateKey, caCert *x509.Certificate) error {
63+
crl, err := certify.CreateCRL(pkey, caCert)
4564
if err != nil {
4665
return err
4766
}
4867

49-
return store(caCert.String(), path)
68+
return store(crl.String(), caCRLPath)
5069
}
5170

5271
func generateCert(pkey *ecdsa.PrivateKey, args []string) (err error) {

cmd/certify/helper_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestGeneratePrivateKeyAndCA(t *testing.T) {
1616
t.Fatal(err)
1717
}
1818

19-
if err := generateCA(pkey.PrivateKey, []string{"cn:local"}, caPath); err != nil {
19+
if _, err := generateCA(pkey.PrivateKey, []string{"cn:local"}, caPath); err != nil {
2020
t.Fatal(err)
2121
}
2222

cmd/certify/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Flags:
4343
var (
4444
caPath = "ca-cert.pem"
4545
caKeyPath = "ca-key.pem"
46+
caCRLPath = "ca-crl.pem"
4647
caInterPath = "ca-intermediate.pem"
4748
caInterKeyPath = "ca-intermediate-key.pem"
4849
Version = "No version provided"

cmd/certify/main_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestRunMain(t *testing.T) {
3838

3939
os.Remove(caPath)
4040
os.Remove(caKeyPath)
41+
os.Remove(caCRLPath)
4142
},
4243
},
4344
{
@@ -54,6 +55,7 @@ func TestRunMain(t *testing.T) {
5455

5556
os.Remove(caPath)
5657
os.Remove(caKeyPath)
58+
os.Remove(caCRLPath)
5759
},
5860
},
5961
{
@@ -138,6 +140,7 @@ func TestRunMain(t *testing.T) {
138140

139141
os.Remove(caPath)
140142
os.Remove(caKeyPath)
143+
os.Remove(caCRLPath)
141144
os.Remove(caInterPath)
142145
os.Remove(caInterKeyPath)
143146
},
@@ -162,6 +165,7 @@ func TestRunMain(t *testing.T) {
162165

163166
os.Remove(caPath)
164167
os.Remove(caKeyPath)
168+
os.Remove(caCRLPath)
165169
os.Remove("nothinux.pem")
166170
os.Remove("nothinux-key.pem")
167171
},

crl.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package certify
2+
3+
import (
4+
"bytes"
5+
"crypto/ecdsa"
6+
"crypto/rand"
7+
"crypto/x509"
8+
"crypto/x509/pkix"
9+
"encoding/pem"
10+
"math/big"
11+
"time"
12+
)
13+
14+
// CertRevocationList hold certificate revocation list
15+
type CertRevocationList struct {
16+
Byte []byte
17+
}
18+
19+
// CreateCRL Create certificate revocation list
20+
func CreateCRL(pkey *ecdsa.PrivateKey, caCert *x509.Certificate) (*CertRevocationList, error) {
21+
crlNumber := time.Now().UTC().Format("20060102150405")
22+
num, _ := big.NewInt(0).SetString(crlNumber, 10)
23+
24+
crl, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
25+
RevokedCertificates: []pkix.RevokedCertificate{},
26+
Number: num,
27+
ThisUpdate: time.Now(),
28+
NextUpdate: time.Now().Add(time.Hour * 48),
29+
}, caCert, pkey)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
return &CertRevocationList{Byte: crl}, nil
35+
}
36+
37+
// String return string of certificate revocation list in pem encoded format
38+
func (c *CertRevocationList) String() string {
39+
var w bytes.Buffer
40+
if err := pem.Encode(&w, &pem.Block{
41+
Type: "X509 CRL",
42+
Bytes: c.Byte,
43+
}); err != nil {
44+
return ""
45+
}
46+
47+
return w.String()
48+
}

crl_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package certify
2+
3+
import (
4+
"crypto/sha1"
5+
"crypto/x509"
6+
"crypto/x509/pkix"
7+
"net"
8+
"testing"
9+
"time"
10+
)
11+
12+
func TestCreateCRL(t *testing.T) {
13+
pkey, err := GetPrivateKey()
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
b, err := x509.MarshalPKIXPublicKey(&pkey.PublicKey)
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
ski := sha1.Sum(b)
24+
25+
template := Certificate{
26+
Subject: pkix.Name{
27+
Organization: []string{"certify"},
28+
CommonName: "certify",
29+
},
30+
NotBefore: time.Now(),
31+
NotAfter: time.Now().Add(24 * time.Hour),
32+
IsCA: true,
33+
SubjectKeyId: ski[:],
34+
DNSNames: []string{"github.com"},
35+
IPAddress: []net.IP{
36+
net.ParseIP("127.0.0.1"),
37+
},
38+
}
39+
40+
t.Run("Test Create CRL with cert that doesn't have keyUsage", func(t *testing.T) {
41+
template1 := template
42+
43+
caCert, err := template1.GetCertificate(pkey.PrivateKey)
44+
if err != nil {
45+
t.Fatal(err)
46+
}
47+
48+
_, err = CreateCRL(pkey.PrivateKey, caCert.Cert)
49+
if err == nil {
50+
t.Fatalf("this should be error, because the cert doesn't have keyUsage")
51+
}
52+
})
53+
54+
t.Run("Test Create CRL", func(t *testing.T) {
55+
template2 := template
56+
template2.KeyUsage = x509.KeyUsageCRLSign
57+
58+
caCert, err := template2.GetCertificate(pkey.PrivateKey)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
63+
_, err = CreateCRL(pkey.PrivateKey, caCert.Cert)
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
})
68+
}

go.mod

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
module github.com/nothinux/certify
22

3-
go 1.17
3+
go 1.21
4+
5+
require (
6+
github.com/manifoldco/promptui v0.9.0
7+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
8+
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78
9+
)
410

511
require (
612
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
7-
github.com/manifoldco/promptui v0.9.0 // indirect
813
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
914
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
10-
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
11-
software.sslmate.com/src/go-pkcs12 v0.0.0-20210415151418-c5206de65a78 // indirect
1215
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
12
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
23
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
34
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
5+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
46
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
7+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
59
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
610
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
711
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

0 commit comments

Comments
 (0)