func (s *Store) validateCert(certID string, c *fdb.Collection) error { ss, err := fdb.String(c.Open("url")) if err != nil { return err } ss = strings.TrimSpace(ss) if !acmeapi.ValidURL(ss) { return fmt.Errorf("certificate has invalid URI") } actualCertID := determineCertificateID(ss) if certID != actualCertID { return fmt.Errorf("cert ID mismatch: %#v != %#v", certID, actualCertID) } crt := &Certificate{ URL: ss, Certificates: nil, Cached: false, } fullchain, err := fdb.Bytes(c.Open("fullchain")) if err == nil { certs, err := acmeutils.LoadCertificates(fullchain) if err != nil { return err } xcrt, err := x509.ParseCertificate(certs[0]) if err != nil { return err } keyID := determineKeyIDFromCert(xcrt) crt.Key = s.keys[keyID] if crt.Key != nil { err := c.WriteLink("privkey", fdb.Link{"keys/" + keyID + "/privkey"}) if err != nil { return err } } crt.Certificates = certs crt.Cached = true } // TODO: obtain derived data s.certs[certID] = crt return nil }
// The Let's Encrypt state directory format keeps certificates but not their // URLs. Since boulder uses the serial number to form the URL, we can // reconstruct the URL. But since not even the provider association is stored, // we have to guess. func determineLECertificateURL(certFilename string) (string, error) { b, err := ioutil.ReadFile(certFilename) if err != nil { return "", err } certs, err := acmeutils.LoadCertificates(b) if err != nil { return "", err } if len(certs) == 0 { return "", fmt.Errorf("no certs") } c, err := x509.ParseCertificate(certs[0]) if err != nil { return "", err } sn := fmt.Sprintf("%036x", c.SerialNumber) for u := range knownProviderURLs { certURL, err := convertBoulderProviderURLToCertificateURL(u, sn) if err != nil { continue } cl := acmeapi.Client{ DirectoryURL: u, } crt := acmeapi.Certificate{ URI: certURL, } err = cl.LoadCertificate(&crt, context.TODO()) if err != nil { continue } return certURL, nil } return "", fmt.Errorf("cannot find certificate URL for %#v (serial %#v)", certFilename, sn) }