Skip to content

Commit c12085b

Browse files
authored
(MAINT) Add ability to generate a CRL to certificates package. (#54)
1 parent 64f50e6 commit c12085b

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

cmd/cert-generator/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func main() {
2828
commonName := flag.String("cn", "localhost", "common name for certificate")
2929
directory := flag.String("directory", wd, "output to generate certs to")
3030
generateCAFiles := flag.Bool("cafiles", false, "whether to output generated CA certs or not")
31+
generateCRLFile := flag.Bool("crlfile", false, "whether to output a CRL or not")
3132
caCert := flag.String("cacertfile", "", "The location of the CA certificate file.")
3233
caKey := flag.String("cakeyfile", "", "The location of the CA key file.")
3334

@@ -95,4 +96,19 @@ func main() {
9596
fmt.Printf("Failed to write TLS key file to disk: %s.", err)
9697
os.Exit(errorExitCode)
9798
}
99+
100+
if generateCRLFile != nil && *generateCRLFile {
101+
crl, err := certificate.GenerateCRL(CAKeyPair)
102+
if err != nil {
103+
fmt.Printf("Failed to generate CRL file: %s.", err)
104+
os.Exit(errorExitCode)
105+
}
106+
107+
err = os.WriteFile(filepath.Join(filepath.Clean(*directory), "tls.crl"), crl,
108+
fileModeUserReadWriteOnly)
109+
if err != nil {
110+
fmt.Printf("Failed to write CRL file to disk: %s.", err)
111+
os.Exit(errorExitCode)
112+
}
113+
}
98114
}

pkg/certificate/certificate.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const (
2323
numberOfHoursInYear = 8760
2424
)
2525

26+
// ErrFailedToDecodeKey indicates that the private key could not be decoded.
27+
var ErrFailedToDecodeKey = fmt.Errorf("unable to decode private key")
28+
2629
// KeyPair stores a PEM encoded certificate and
2730
// a PEM encoded RSA private key.
2831
type KeyPair struct {
@@ -75,7 +78,7 @@ func GenerateCA() (*KeyPair, error) {
7578
IsCA: true,
7679
SubjectKeyId: subjectKeyID[:],
7780
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
78-
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
81+
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
7982
BasicConstraintsValid: true,
8083
}
8184

@@ -163,6 +166,42 @@ func GenerateSignedCert(ca *KeyPair, hostnames HostNames, commonName string) (*K
163166
return &keyPair, nil
164167
}
165168

169+
// GenerateCRL will generate a blank Certificate revocation List from the provided issuer certificate.
170+
func GenerateCRL(ca *KeyPair) ([]byte, error) {
171+
tlsKeyPair, err := tls.X509KeyPair(ca.Certificate, ca.PrivateKey)
172+
if err != nil {
173+
return nil, fmt.Errorf("can't convert to X509KeyPair because: %w", err)
174+
}
175+
176+
issuerCert, err := x509.ParseCertificate(tlsKeyPair.Certificate[0])
177+
if err != nil {
178+
return nil, fmt.Errorf("%w", err)
179+
}
180+
181+
privateKey, _ := pem.Decode(ca.PrivateKey)
182+
if privateKey == nil {
183+
return nil, ErrFailedToDecodeKey
184+
}
185+
186+
crlKey, err := x509.ParsePKCS1PrivateKey(privateKey.Bytes)
187+
if err != nil {
188+
return nil, fmt.Errorf("%w", err)
189+
}
190+
191+
crlTemplate := &x509.RevocationList{
192+
Number: generateSerialNumber(),
193+
ThisUpdate: time.Now(),
194+
NextUpdate: time.Now().Add(time.Hour * 24 * 365 * 10), // Set to be a large time in the future.
195+
}
196+
197+
crlList, err := x509.CreateRevocationList(rand.Reader, crlTemplate, issuerCert, crlKey)
198+
if err != nil {
199+
return nil, fmt.Errorf("%w", err)
200+
}
201+
202+
return pem.EncodeToMemory(&pem.Block{Type: "X509 CRL", Bytes: crlList}), nil
203+
}
204+
166205
func generateSerialNumber() *big.Int {
167206
// choose a random number between 0 and 999999999999999999
168207
upperLimitForRandomNumber := 999999999999999999

pkg/certificate/certificate_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,34 @@ func TestGenerateCAWorks(t *testing.T) {
2020
}
2121
}
2222

23+
func TestGenerateCRLWorks(t *testing.T) {
24+
keyPair, err := GenerateCA()
25+
if err != nil {
26+
t.Errorf("Unable to generate cert due to %s", err)
27+
}
28+
29+
roots := x509.NewCertPool()
30+
ok := roots.AppendCertsFromPEM([]byte(keyPair.Certificate))
31+
if !ok {
32+
t.Error("failed to parse root certificate")
33+
}
34+
35+
crlPem, err := GenerateCRL(keyPair)
36+
if err != nil {
37+
t.Errorf("failed to generate CRL due to error %s", err)
38+
}
39+
40+
crl, _ := pem.Decode(crlPem)
41+
if crl == nil {
42+
t.Error("failed to parse CRL due to nil response from pem decode")
43+
}
44+
45+
_, err = x509.ParseRevocationList(crl.Bytes)
46+
if err != nil {
47+
t.Errorf("failed to parse CRL due to error %s", err)
48+
}
49+
}
50+
2351
func TestGenerateCertLocalhostWorks(t *testing.T) {
2452
rootKeyPair, err := GenerateCA()
2553
if err != nil {

scripts/generate-cert.sh

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ else
4848
read generateca
4949
validate_choice $generateca
5050
if [ "$generateca" == "y" ];then
51-
CA_ARGS="-cafiles true"
51+
CA_ARGS="-cafiles"
5252
fi
5353
fi
5454

55+
echo "Do you want to generate a CRL?(y|n)"
56+
read generatecrl
57+
validate_choice $generatecrl
58+
if [ "$generatecrl" == "y" ];then
59+
CRL_ARGS="-crlfile"
60+
fi
61+
5562
echo "Enter the comman name(cn) you want to generate the certificate for(localhost):"
5663
read commonname < /dev/stdin
5764

@@ -84,5 +91,5 @@ else
8491
done
8592
fi
8693

87-
ARGS="$DIR_ARGS $CA_ARGS $CN_ARGS $HOST_ARGS"
94+
ARGS="$DIR_ARGS $CA_ARGS $CN_ARGS $CRL_ARGS $HOST_ARGS"
8895
go run cmd/cert-generator/main.go $ARGS

0 commit comments

Comments
 (0)