diff --git a/README.md b/README.md index 454835c..43c85ce 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Contents: certchain/ Display the certificate chain from a TLS connection. certdump/ Dump certificate information. + certverify/ Verify a TLS X.509 certificate. clustersh/ Run commands or transfer files across multiple servers via SSH. csrpubdump/ Dump the public key from an X.509 diff --git a/cmd/certverify/README b/cmd/certverify/README new file mode 100644 index 0000000..66cdc4b --- /dev/null +++ b/cmd/certverify/README @@ -0,0 +1,36 @@ +certverify + +This is a small utility to verify a TLS X.509 certificate. It returns +0 on success; on error, it prints the error and returns with exit code 1. +It does not check for revocations (though this is a planned feature), +and it does not check the hostname (it deals only in certificate files). + +[ Usage ] + certverify [-ca bundle] [-f] [-i bundle] [-v] certificate + +[ Flags ] + -ca bundle Specify the path to the CA certificate bundle + to use. + -f Force the use of the intermediate bundle, ignoring + any intermediates bundled with the certificate. + -i bundle Specify the path to the intermediate certificate + bundle to use. + -v Print extra information during the program's run. + If the certificate validates, also prints 'OK'. + +[ Examples ] + +To verify the 'www.pem' certificate against the system roots: + + $ certverify www.pem + $ echo $? + 0 + +To verify the 'www.pem' certificate against the 'ca-cert.pem' CA +certificate bundle, and seeing a mismatch: + + $ certverify -ca ca-cert.pem www.pem + Verification failed: x509: certificate signed by unknown authority + $ echo $? + 1 + diff --git a/cmd/certverify/main.go b/cmd/certverify/main.go new file mode 100644 index 0000000..c1d9b69 --- /dev/null +++ b/cmd/certverify/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "crypto/x509" + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/cloudflare/cfssl/helpers" + "github.com/kisom/die" + "github.com/kisom/goutils/lib" +) + +func main() { + var caFile, intFile string + var forceIntermediateBundle, verbose bool + flag.StringVar(&caFile, "ca", "", "CA certificate `bundle`") + flag.StringVar(&intFile, "i", "", "intermediate `bundle`") + flag.BoolVar(&forceIntermediateBundle, "f", false, + "force the use of the intermediate bundle, ignoring any intermediates bundled with certificate") + flag.BoolVar(&verbose, "v", false, "verbose") + flag.Parse() + + var roots *x509.CertPool + if caFile != "" { + var err error + if verbose { + fmt.Println("[+] loading root certificates from", caFile) + } + roots, err = helpers.LoadPEMCertPool(caFile) + die.If(err) + } + + var ints *x509.CertPool + if intFile != "" { + var err error + if verbose { + fmt.Println("[+] loading intermediate certificates from", intFile) + } + ints, err = helpers.LoadPEMCertPool(caFile) + die.If(err) + } else { + ints = x509.NewCertPool() + } + + if flag.NArg() != 1 { + fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert", + lib.ProgName()) + } + + fileData, err := ioutil.ReadFile(flag.Arg(0)) + die.If(err) + + chain, err := helpers.ParseCertificatesPEM(fileData) + die.If(err) + if verbose { + fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain)) + } + + cert := chain[0] + if len(chain) > 1 { + if !forceIntermediateBundle { + for _, intermediate := range chain[1:] { + if verbose { + fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId) + } + + ints.AddCert(intermediate) + } + } + } + + opts := x509.VerifyOptions{ + Intermediates: ints, + Roots: roots, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + } + + _, err = cert.Verify(opts) + if err != nil { + fmt.Fprintf(os.Stderr, "Verification failed: %v\n", err) + os.Exit(1) + } + + if verbose { + fmt.Println("OK") + } +}