Skip to content

Commit

Permalink
feat: verify cert against crl (#18)
Browse files Browse the repository at this point in the history
Introduce new flag verify-crl to verify certificate against crl
  • Loading branch information
nothinux authored Sep 7, 2023
1 parent 25d1184 commit c701791
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 13 deletions.
31 changes: 26 additions & 5 deletions cmd/certify/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,22 +185,43 @@ func createCertificate(args []string) error {
return nil
}

func revokeCertificate(args []string) (string, error) {
func verifyCertificate(args []string) error {
if len(args) < 4 {
return "", fmt.Errorf("you need to provide cert file and crl file")
return fmt.Errorf("you need to provide cert file and crl file")
}

certByte, err := os.ReadFile(args[2])
cert, err := readCertificateFile(args[2])
if err != nil {
return "", err
return err
}

crl, err := readCRLFile(args[3])
if err != nil {
return err
}

for _, sn := range crl.RevokedCertificateEntries {
if sn.SerialNumber.Cmp(cert.SerialNumber) == 0 {
fmt.Printf("%s\ncode: %d\ncertificate revoked at %v\n", cert.Subject.String(), sn.ReasonCode, sn.RevocationTime.Format("2006-01-02 15:04:05"))
return fmt.Errorf("error %s verification failed", args[2])
}
}

fmt.Printf("%s: OK\n", args[2])
return nil
}

func revokeCertificate(args []string) (string, error) {
if len(args) < 4 {
return "", fmt.Errorf("you need to provide cert file and crl file")
}

crlBytes, err := os.ReadFile(args[3])
if err != nil {
return "", err
}

cert, err := certify.ParseCertificate(certByte)
cert, err := readCertificateFile(args[2])
if err != nil {
return "", err
}
Expand Down
84 changes: 84 additions & 0 deletions cmd/certify/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,90 @@ func TestRevokeCertificate(t *testing.T) {
}
}

func TestVerifyCertificate(t *testing.T) {
// TODO: add test reading certificate from stdin
tests := []struct {
Name string
Args []string
expectedOutput string
expectedError string
}{
{
Name: "Test verify certificate",
Args: []string{"certify", "-verify", "nothinux.local.pem", "ca-crl.pem"},
expectedOutput: "nothinux.local.pem: OK",
},
{
Name: "Test verify certificate with not enough argument",
Args: []string{"certify", "-verify", "ca-crl.pem"},
expectedError: "you need to provide cert file and crl file",
},
{
Name: "Test verify certificate with wrong crl file",
Args: []string{"certify", "-verify", "nothinux.local.pem", "ca-cert.pem"},
expectedError: "x509: unsupported crl version",
},
{
Name: "Test verify certificate with wrong cert file",
Args: []string{"certify", "-revoke", "ca-crl.pem", "ca-crl.pem"},
expectedError: "x509: malformed validity",
},
{
Name: "Test verify certificate with revoked cert file",
Args: []string{"certify", "-revoke", "nothinux.local.pem", "ca-crl.pem"},
expectedError: "error nothinux.local.pem verification failed",
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := initCA([]string{"certify", "-init"}); err != nil {
t.Fatal(err)
}

if err := createCertificate([]string{"certify", "nothinux.local"}); err != nil {
t.Fatal(err)
}

if tt.expectedOutput != "" {
err := verifyCertificate(tt.Args)
if err != nil {
t.Fatal(err)
}
cleanupfiles([]string{caPath, caKeyPath, caCRLPath, "nothinux.local.pem", "nothinux.local-key.pem"})
return
}

crlPath, err := revokeCertificate(tt.Args)
if err != nil {
if !strings.Contains(err.Error(), tt.expectedError) {
t.Fatalf("got %v, want %v", err, tt.expectedError)
}
cleanupfiles([]string{caPath, caKeyPath, caCRLPath, "nothinux.local.pem", "nothinux.local-key.pem"})
return
}

// replace last element
tt.Args = tt.Args[:len(tt.Args)-1]
tt.Args = append(tt.Args, crlPath)

// verify revoked cert
err = verifyCertificate(tt.Args)
if err != nil {
if !strings.Contains(err.Error(), tt.expectedError) {
t.Fatalf("got %v, want %v", err, tt.expectedError)
}
cleanupfiles([]string{caPath, caKeyPath, caCRLPath, crlPath, "nothinux.local.pem", "nothinux.local-key.pem"})
return
}

t.Cleanup(func() {
cleanupfiles([]string{caPath, caKeyPath, caCRLPath, crlPath, "nothinux.local.pem", "nothinux.local-key.pem"})
})
})
}
}

func TestReadRemoteCertificate(t *testing.T) {
tests := []struct {
Name string
Expand Down
10 changes: 7 additions & 3 deletions cmd/certify/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,22 @@ func getCACert(path string) (*x509.Certificate, error) {
return c, nil
}

func readCertificateFile(path string) (*x509.Certificate, error) {
func readCRLFile(path string) (*x509.RevocationList, error) {
f, err := os.ReadFile(path)
if err != nil {
return nil, err
}

c, err := certify.ParseCertificate(f)
return certify.ParseCRL(f)
}

func readCertificateFile(path string) (*x509.Certificate, error) {
f, err := os.ReadFile(path)
if err != nil {
return nil, err
}

return c, nil
return certify.ParseCertificate(f)
}

func getPfxData(pkey, cert, caCert, password string) ([]byte, error) {
Expand Down
17 changes: 12 additions & 5 deletions cmd/certify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@ Flags:
-read <filename>
Read certificate information from file or stdin
-read-crl <filename>
Read certificate revocation list from file or stdin
Read certificate revocation list from file or stdin
-connect <host:443> <tlsver:1.2> <insecure> <with-ca:ca-path>
Show certificate information from remote host, use tlsver to set spesific tls version
-export-p12 <cert> <private-key> <ca-cert>
Generate client.p12 pem file containing certificate, private key and ca certificate
-match <private-key> <cert>
Verify cert-key.pem and cert.pem has same public key
-interactive
Run certify interactively
-revoke <ca-cert> <ca-private-key>
Revoke certificate, the certificate will be added to CRL
Run certify interactively
-revoke <certificate> <crl-file>
Revoke certificate, the certificate will be added to CRL
-verify-crl <certificate> <crl-file>
Check if the certificate was revoked
-version
print certify version
`
Expand Down Expand Up @@ -70,7 +72,8 @@ func runMain() error {
connect = flag.Bool("connect", false, "show information about certificate on remote host")
epkcs12 = flag.Bool("export-p12", false, "export certificate and key to pkcs12 format")
interactive = flag.Bool("interactive", false, "run certify interactively")
revoke = flag.Bool("revoke", false, "Revoke certificate, the certificate will be added to CRL")
revoke = flag.Bool("revoke", false, "revoke certificate, the certificate will be added to CRL")
verifycrl = flag.Bool("verify-crl", false, "check if the certificate was revoked")
)

flag.Usage = func() {
Expand Down Expand Up @@ -134,6 +137,10 @@ func runMain() error {
return err
}

if *verifycrl {
return verifyCertificate(os.Args)
}

if *epkcs12 {
if len(os.Args) < 5 {
fmt.Println("you must provide [key-path] [cert-path] and [ca-path]")
Expand Down

0 comments on commit c701791

Please sign in to comment.