capi/lib/revocation/crl/crl.go (112 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 crl import ( "crypto/x509" "fmt" "github.com/pkg/errors" "io/ioutil" "math/big" "net/http" "strings" "time" ) type CRLStatus string const ( Good CRLStatus = "good" Revoked = "revoked" Unchecked = "unchecked" BadResponse = "badResponse" ) type CRL struct { Error string Endpoint string Status CRLStatus } func VerifyChain(chain []*x509.Certificate) [][]CRL { crls := make([][]CRL, len(chain)) if len(chain) == 1 { return crls } for i, cert := range chain[:len(chain)-1] { crls[i] = queryCRLs(cert) } crls[len(crls)-1] = make([]CRL, 0) return crls } func queryCRLs(certificate *x509.Certificate) []CRL { statuses := make([]CRL, len(certificate.CRLDistributionPoints)) for i, url := range certificate.CRLDistributionPoints { statuses[i] = newCRL(certificate.SerialNumber, url) } if disagreement := allAgree(statuses); disagreement != nil { for _, status := range statuses { status.Error = disagreement.Error() } } return statuses } func allAgree(statuses []CRL) error { if len(statuses) <= 1 { return nil } checkedCRLs := make([]CRL, 0) for _, s := range statuses { if s.Status == Unchecked { continue } checkedCRLs = append(checkedCRLs, s) } firstAnswer := checkedCRLs[0] for _, otherAnswer := range checkedCRLs[1:] { if otherAnswer.Status != firstAnswer.Status { return errors.New("The listed CRLs disagree with each other") } } return nil } func newCRL(serialNumber *big.Int, distributionPoint string) (crl CRL) { crl.Endpoint = distributionPoint if strings.HasPrefix(distributionPoint, "ldap") { crl.Status = Unchecked return } req, err := http.NewRequest("GET", distributionPoint, nil) req.Header.Add("X-Automated-Tool", "https://github.com/mozilla/CCADB-Tools/capi CCADB test website verification tool") client := http.Client{} client.Timeout = time.Duration(20 * time.Second) raw, err := client.Do(req) if err != nil { crl.Error = errors.Wrapf(err, "failed to retrieve CRL from distribution point %v", distributionPoint).Error() crl.Status = BadResponse return } defer raw.Body.Close() if raw.StatusCode != http.StatusOK { crl.Error = errors.New(fmt.Sprintf("wanted 200 response, got %d", raw.StatusCode)).Error() crl.Status = BadResponse return } b, err := ioutil.ReadAll(raw.Body) if err != nil { crl.Error = errors.Wrapf(err, "failed to read response from CRL distribution point %v", distributionPoint).Error() crl.Status = BadResponse return } c, err := x509.ParseCRL(b) if err != nil { crl.Error = errors.Wrapf(err, "failed to parse provided CRL\n%v", raw).Error() crl.Status = BadResponse return } if c.TBSCertList.RevokedCertificates == nil { crl.Status = Good return } for _, revoked := range c.TBSCertList.RevokedCertificates { if revoked.SerialNumber.Cmp(serialNumber) == 0 { crl.Status = Revoked return } } crl.Status = Good return }