capi/lib/certificateUtils/parseChain.go (126 lines of code) (raw):

/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package certificateUtils import ( "bytes" "crypto" "crypto/tls" "crypto/x509" "encoding/pem" "fmt" log "github.com/sirupsen/logrus" "net/http" "regexp" "strings" "time" ) const ( BeginCertificate = `-----BEGIN CERTIFICATE-----` EndCertificate = `-----END CERTIFICATE-----` ) func ParseChain(provided []byte) (chain []*x509.Certificate, err error) { var cert *x509.Certificate provided = bytes.TrimSpace(provided) for _, c := range bytes.SplitAfter(provided, []byte(EndCertificate)) { if len(c) == 0 { continue } var fmtedPEM []byte fmtedPEM, err = NormalizePEM(c) if err != nil { return } block, _ := pem.Decode(fmtedPEM) cert, err = x509.ParseCertificate(block.Bytes) if err != nil { log.WithFields(log.Fields{ "fullchain": string(provided), "failedMember": string(fmtedPEM), }) log.WithError(err) log.WithTime(time.Now()) log.Error("An individual member of a chain provided to ParseChain failed to be parsed by x509.ParseCertificate") return } chain = append(chain, cert) } return } func GatherCertificateChain(subjectURL string) ([]*x509.Certificate, error) { log.WithField("SubjectURL", subjectURL).Info("Gathering certificates") // This is very mandatory otherwise the HTTP package will vomit on revoked/expired certificates and return an error. transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} client := &http.Client{Transport: transport} // You have twenty seconds to comply. client.Timeout = time.Duration(20 * time.Second) req, err := http.NewRequest("GET", subjectURL, nil) if err != nil { return []*x509.Certificate{}, err } req.Header.Add("X-Automated-Tool", "https://github.com/mozilla/CCADB-Tools/capi CCADB test website verification tool") resp, err := client.Do(req) if err != nil { return []*x509.Certificate{}, err } if err := resp.Body.Close(); err != nil { log.WithField("URL", subjectURL) log.WithError(err) log.WithTime(time.Now()) log.Warnln("The body of the HTTP response failed to close when retrieving a subject's certificate " + "chain. This is generally fine as we do not use any content within the body, however a failure from the " + "remote server to response may be concerning.") } return resp.TLS.PeerCertificates, err } func EmplaceRoot(chain []*x509.Certificate, root *x509.Certificate) []*x509.Certificate { switch IncludesTrustAnchor(chain) { case true: chain[len(chain)-1] = root return chain default: return append(chain, root) } } func IncludesTrustAnchor(chain []*x509.Certificate) bool { if len(chain) == 0 { return false } anchor := chain[len(chain)-1] return bytes.Equal(anchor.RawSubject, anchor.RawIssuer) } func BrokenEdges(chain []*x509.Certificate) [][2]Fingerprint { brokenEdges := make([][2]Fingerprint, 0) for i, cert := range chain[:len(chain)-1] { issuer := chain[i+1] if !bytes.Equal(cert.RawIssuer, issuer.RawSubject) { brokenEdges = append(brokenEdges, [2]Fingerprint{FingerprintOf(cert), FingerprintOf(issuer)}) } } return brokenEdges } var pemStripper = regexp.MustCompile( `(` + strings.Join([]string{`\n`, `'`, BeginCertificate, EndCertificate}, `|`) + `)`) // NormalizePEM ignores any formatting or string artifacts that the PEM may have had // and applies https://tools.ietf.org/html/rfc1421 func NormalizePEM(pem []byte) (fmtedPEM []byte, err error) { defer func() { if e := recover(); e != nil { err = e.(error) } }() pem = pemStripper.ReplaceAll(pem, []byte{}) fmtedPEM = append(fmtedPEM, BeginCertificate+"\n"...) width := 64 // Columns per line https://tools.ietf.org/html/rfc1421 for len(pem) > 0 { if len(pem) < width { width = len(pem) } fmtedPEM = append(fmtedPEM, pem[:width]...) fmtedPEM = append(fmtedPEM, '\n') pem = pem[width:] } fmtedPEM = append(fmtedPEM, EndCertificate...) return } type Fingerprint = string func FingerprintOf(cert *x509.Certificate) Fingerprint { hasher := crypto.SHA256.New() hasher.Write(cert.Raw) return fmt.Sprintf("%X", hasher.Sum(nil)) }