goutils/cmd/subjhash/main.go

114 lines
2.3 KiB
Go

package main
import (
"bytes"
"crypto/sha256"
"crypto/x509"
"flag"
"fmt"
"io"
"os"
"git.wntrmute.dev/kyle/goutils/certlib"
"git.wntrmute.dev/kyle/goutils/die"
"git.wntrmute.dev/kyle/goutils/lib"
)
func init() {
flag.Usage = func() { usage(os.Stdout); os.Exit(1) }
}
func usage(w io.Writer) {
fmt.Fprintf(w, `Print hash of subject or issuer fields in certificates.
Usage: subjhash [-im] certs...
Flags:
-i Print hash of issuer field.
-m Matching mode. This expects arguments to be in the form of
pairs of certificates (e.g. previous, new) whose subjects
will be compared. For example,
subjhash -m ca1.pem ca1-renewed.pem \
ca2.pem ca2-renewed.pem
will exit with a non-zero status if the subject in the
ca1-renewed.pem certificate doesn't match the subject in the
ca.pem certificate; similarly for ca2.
`)
}
// NB: the Issuer field is *also* a subject field. Also, the returned
// hash is *not* hex encoded.
func getSubjectInfoHash(cert *x509.Certificate, issuer bool) []byte {
if cert == nil {
return nil
}
var subject []byte
if issuer {
subject = cert.RawIssuer
} else {
subject = cert.RawSubject
}
digest := sha256.Sum256(subject)
return digest[:]
}
func printDigests(paths []string, issuer bool) {
for _, path := range paths {
cert, err := certlib.LoadCertificate(path)
if err != nil {
lib.Warn(err, "failed to load certificate from %s", path)
continue
}
digest := getSubjectInfoHash(cert, issuer)
fmt.Printf("%x %s\n", digest, path)
}
}
func matchDigests(paths []string, issuer bool) {
if (len(paths) % 2) != 0 {
lib.Errx(lib.ExitFailure, "not all certificates are paired")
}
var invalid int
for {
if len(paths) == 0 {
break
}
fst := paths[0]
snd := paths[1]
paths = paths[2:]
fstCert, err := certlib.LoadCertificate(fst)
die.If(err)
sndCert, err := certlib.LoadCertificate(snd)
die.If(err)
if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) {
lib.Warnx("certificates don't match: %s and %s", fst, snd)
invalid++
}
}
if invalid > 0 {
os.Exit(1)
}
}
func main() {
var issuer, match bool
flag.BoolVar(&issuer, "i", false, "print the issuer")
flag.BoolVar(&match, "m", false, "match mode")
flag.Parse()
paths := flag.Args()
if match {
matchDigests(paths, issuer)
} else {
printDigests(paths, issuer)
}
}