// NewSourceFromFile reads the named file into an InMemorySource. // The file read by this function must contain whitespace-separated OCSP // responses. Each OCSP response must be in base64-encoded DER form (i.e., // PEM without headers or whitespace). Invalid responses are ignored. // This function pulls the entire file into an InMemorySource. func NewSourceFromFile(responseFile string) (Source, error) { fileContents, err := ioutil.ReadFile(responseFile) if err != nil { return nil, err } responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1) src := InMemorySource{} for _, b64 := range responsesB64 { // if the line/space is empty just skip if b64 == "" { continue } der, tmpErr := base64.StdEncoding.DecodeString(b64) if tmpErr != nil { log.Errorf("Base64 decode error on: %s", b64) continue } response, tmpErr := ocsp.ParseResponse(der, nil) if tmpErr != nil { log.Errorf("OCSP decode error on: %s", b64) continue } src[response.SerialNumber.String()] = der } log.Infof("Read %d OCSP responses", len(src)) return src, nil }
// sendOCSPRequest attempts to request an OCSP response from the // server. The error only indicates a failure to *fetch* the // certificate, and *does not* mean the certificate is valid. func sendOCSPRequest(server string, req []byte, issuer *x509.Certificate) (ocspResponse *ocsp.Response, err error) { var resp *http.Response if len(req) > 256 { buf := bytes.NewBuffer(req) resp, err = http.Post(server, "application/ocsp-request", buf) } else { reqURL := server + "/" + base64.StdEncoding.EncodeToString(req) resp, err = http.Get(reqURL) } if err != nil { return } if resp.StatusCode != http.StatusOK { return } body, err := ioutil.ReadAll(resp.Body) if err != nil { return } resp.Body.Close() if bytes.Equal(body, ocspUnauthorised) { return } if bytes.Equal(body, ocspMalformed) { return } return ocsp.ParseResponse(body, issuer) }
func (cp *TLSConf) OCSP() (err error) { if cp.IsEnabled() && len(cp.kp.Certificate) > 1 { for _, ocsp_server := range cp.cert.OCSPServer { for _, issuing := range cp.cert.IssuingCertificateURL { //log.Println("OCSP : ["+ocsp_server+"] ["+issuing+"]") issuer, err := load_issuer(issuing) if err != nil { return err } request, err := ocsp.CreateRequest(cp.cert, issuer, &ocsp.RequestOptions{crypto.SHA1}) if err != nil { return err } staple := get_or_post_OCSP(ocsp_server, "application/ocsp-request", request) if len(staple) < MIN_STAPLE_SIZE { return nil } _, err = ocsp.ParseResponse(staple, issuer) //log.Printf("\n%+v\n", struct{ // ProducedAt, ThisUpdate, NextUpdate string // }{ resp.ProducedAt.Format(time.RFC3339), resp.ThisUpdate.Format(time.RFC3339), resp.NextUpdate.Format(time.RFC3339) } ) if err == nil { cp.kp.OCSPStaple = staple return nil } } } } return err }
// DeleteOldStapleFiles deletes cached OCSP staples that have expired. // TODO: Should we do this for certificates too? func DeleteOldStapleFiles() { files, err := ioutil.ReadDir(ocspFolder) if err != nil { // maybe just hasn't been created yet; no big deal return } for _, file := range files { if file.IsDir() { // wierd, what's a folder doing inside the OCSP cache? continue } stapleFile := filepath.Join(ocspFolder, file.Name()) ocspBytes, err := ioutil.ReadFile(stapleFile) if err != nil { continue } resp, err := ocsp.ParseResponse(ocspBytes, nil) if err != nil { // contents are invalid; delete it err = os.Remove(stapleFile) if err != nil { log.Printf("[ERROR] Purging corrupt staple file %s: %v", stapleFile, err) } } if time.Now().After(resp.NextUpdate) { // response has expired; delete it err = os.Remove(stapleFile) if err != nil { log.Printf("[ERROR] Purging expired staple file %s: %v", stapleFile, err) } } } }
func main() { issuerFile := flag.String("issuer", "", "Issuer certificate (PEM)") responderFile := flag.String("responder", "", "OCSP responder certificate (DER)") targetFile := flag.String("target", "", "Certificate whose status is being reported (PEM)") pkcs11File := flag.String("pkcs11", "", pkcs11Usage) outFile := flag.String("out", "", "File to which the OCSP response will be written") thisUpdateString := flag.String("thisUpdate", "", "Time for ThisUpdate field, RFC3339 format (e.g. 2016-09-02T00:00:00Z)") nextUpdateString := flag.String("nextUpdate", "", "Time for NextUpdate field, RFC3339 format") status := flag.Int("status", 0, "Status for response (0 = good, 1 = revoked)") flag.Usage = func() { fmt.Fprint(os.Stderr, usage) flag.PrintDefaults() } flag.Parse() if len(*outFile) == 0 { cmd.FailOnError(fmt.Errorf("No output file provided"), "") } thisUpdate, err := time.Parse(time.RFC3339, *thisUpdateString) cmd.FailOnError(err, "Parsing thisUpdate flag") nextUpdate, err := time.Parse(time.RFC3339, *nextUpdateString) cmd.FailOnError(err, "Parsing nextUpdate flag") issuer, responder, target, pkcs11, err := readFiles(*issuerFile, *responderFile, *targetFile, *pkcs11File) cmd.FailOnError(err, "Failed to read files") // Instantiate the private key from PKCS11 priv, err := pkcs11key.New(pkcs11.Module, pkcs11.TokenLabel, pkcs11.PIN, pkcs11.PrivateKeyLabel) cmd.FailOnError(err, "Failed to load PKCS#11 key") // Populate the remaining fields in the template template := ocsp.Response{ SerialNumber: target.SerialNumber, Certificate: responder, Status: *status, ThisUpdate: thisUpdate, NextUpdate: nextUpdate, } if !core.KeyDigestEquals(responder.PublicKey, priv.Public()) { cmd.FailOnError(fmt.Errorf("PKCS#11 pubkey does not match pubkey "+ "in responder certificate"), "loading keys") } // Sign the OCSP response responseBytes, err := ocsp.CreateResponse(issuer, responder, template, priv) cmd.FailOnError(err, "Failed to sign OCSP response") _, err = ocsp.ParseResponse(responseBytes, nil) cmd.FailOnError(err, "Failed to parse signed response") responseBytesBase64 := base64.StdEncoding.EncodeToString(responseBytes) + "\n" // Write the OCSP response to stdout err = ioutil.WriteFile(*outFile, []byte(responseBytesBase64), 0666) cmd.FailOnError(err, "Failed to write output file") }
// GetOCSPForCert takes a PEM encoded cert or cert bundle and returns a OCSP // response from the OCSP endpoint in the certificate. // This []byte can be passed directly into the OCSPStaple property of a tls.Certificate. // If the bundle only contains the issued certificate, this function will try // to get the issuer certificate from the IssuingCertificateURL in the certificate. func GetOCSPForCert(bundle []byte) ([]byte, error) { certificates, err := parsePEMBundle(bundle) if err != nil { return nil, err } // We only got one certificate, means we have no issuer certificate - get it. if len(certificates) == 1 { // TODO: build fallback. If this fails, check the remaining array entries. resp, err := http.Get(certificates[0].IssuingCertificateURL[0]) if err != nil { return nil, err } issuerBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } issuerCert, err := x509.ParseCertificate(issuerBytes) if err != nil { return nil, err } // Insert it into the slice on position 0 // We want it ordered right SRV CRT -> CA certificates = append(certificates, issuerCert) } // We expect the certificate slice to be ordered downwards the chain. // SRV CRT -> CA. We need to pull the cert and issuer cert out of it, // which should always be the last two certificates. issuedCert := certificates[0] issuerCert := certificates[1] // Finally kick off the OCSP request. ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil) if err != nil { return nil, err } reader := bytes.NewReader(ocspReq) req, err := http.Post(issuedCert.OCSPServer[0], "application/ocsp-request", reader) if err != nil { return nil, err } ocspResBytes, err := ioutil.ReadAll(req.Body) _, err = ocsp.ParseResponse(ocspResBytes, nil) if err != nil { log.Printf("OCSPParse Error: %v", err) return nil, err } return ocspResBytes, nil }
func init() { bytes, err := hex.DecodeString(OCSPResponseHex) if err != nil { panic(err) } OCSPResponseBytes = bytes OCSPResponse, err = ocsp.ParseResponse(bytes, nil) if err != nil { panic(err) } }
func (tcc *TLSClientConfig) Verify(conn *tls.Conn) (*TLSState, error) { var ocsprep *ocsp.Response var der []byte var err error res := new(TLSState) cstate := conn.ConnectionState() res.SNIExist = (tcc.SNI != "") res.PKPExist = (tcc.PKPs != nil && len(tcc.PKPs) > 0) if cstate.OCSPResponse != nil { ocsprep, err = ocsp.ParseResponse(cstate.OCSPResponse, nil) if err != nil { return nil, err } res.OCSPExist = true res.OCSPValid = (ocsprep.Status == ocsp.Good) res.OCSPUnknown = (ocsprep.Status == ocsp.Unknown) } for _, peercert := range cstate.PeerCertificates { der, err = x509.MarshalPKIXPublicKey(peercert.PublicKey) if err != nil { return nil, err } if res.SNIExist && !res.SNIValid && peercert.VerifyHostname(tcc.SNI) == nil { res.SNIValid = true } if res.OCSPValid && !res.OCSPChecked && ocsprep.CheckSignatureFrom(peercert) == nil { res.OCSPChecked = true } rawhash := sha256.Sum256(der) hash := base64.StdEncoding.EncodeToString(rawhash[:]) if res.PKPExist { res.PKPCerts++ valid, ok := tcc.PKPs[hash] switch { case ok && valid: res.PKPValid++ case ok && !valid: res.PKPInvalid++ } } } return res, nil }
// Checks OCSP for a certificate. The immediate issuer must be specified. If // the HTTP client is nil, the default client is used. If the certificate does // not support OCSP, (nil, nil) is returned. Uses HTTP GET rather than POST. // The response is verified. The caller must check the response status. func CheckOCSP(httpClient *http.Client, crt, issuer *x509.Certificate) (*ocsp.Response, error) { if httpClient == nil { httpClient = http.DefaultClient } if len(crt.OCSPServer) == 0 { return nil, nil } b, err := ocsp.CreateRequest(crt, issuer, nil) if err != nil { return nil, err } b64 := base64.StdEncoding.EncodeToString(b) path := crt.OCSPServer[0] + "/" + b64 req, err := http.NewRequest("GET", path, nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/ocsp-response") res, err := httpClient.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, fmt.Errorf("OCSP response has status %#v", res.Status) } if res.Header.Get("Content-Type") != "application/ocsp-response" { return nil, fmt.Errorf("response to OCSP request had unexpected content type") } resb, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } return ocsp.ParseResponse(resb, issuer) }
// Checks OCSP for a certificate. The immediate issuer must be specified. If // the certificate does not support OCSP, (nil, nil) is returned. Uses HTTP // GET rather than POST. The response is verified. The caller must check the // response status. The raw OCSP response is also returned, even if parsing // failed and err is non-nil. func (c *Client) CheckOCSPRaw(crt, issuer *x509.Certificate, ctx context.Context) (parsedResponse *ocsp.Response, rawResponse []byte, err error) { if len(crt.OCSPServer) == 0 { return } b, err := ocsp.CreateRequest(crt, issuer, nil) if err != nil { return } b64 := base64.StdEncoding.EncodeToString(b) path := crt.OCSPServer[0] + "/" + b64 req, err := http.NewRequest("GET", path, nil) if err != nil { return } req.Header.Set("Accept", "application/ocsp-response") res, err := c.doReqActual(req, ctx) if err != nil { return } defer res.Body.Close() if res.StatusCode != 200 { err = fmt.Errorf("OCSP response has status %#v", res.Status) return } if res.Header.Get("Content-Type") != "application/ocsp-response" { err = fmt.Errorf("response to OCSP request had unexpected content type") return } // Read response, limiting response to 1MiB. rawResponse, err = ioutil.ReadAll(denet.LimitReader(res.Body, 1*1024*1024)) if err != nil { return } parsedResponse, err = ocsp.ParseResponse(rawResponse, issuer) return }
func TestSignNoResponder(t *testing.T) { req, dur := setup(t) s, err := NewSignerFromFile(serverCertFile, serverCertFile, serverKeyFile, dur) if err != nil { t.Fatalf("Signer creation failed: %v", err) } respBytes, err := s.Sign(req) if err != nil { t.Fatal("Failed to sign with no responder cert") } resp, err := ocsp.ParseResponse(respBytes, nil) if err != nil { t.Fatal("Failed to fail on improper status code") } if resp.Certificate != nil { t.Fatal("Response contain responder cert even though it was identical to issuer") } }
func main() { for _, host := range os.Args[1:] { fmt.Println(host) host := net.JoinHostPort(host, "443") conn, err := tls.Dial("tcp", host, nil) peerCerts := conn.ConnectionState().PeerCertificates resp, err := getResponse(peerCerts[0], peerCerts[1]) if err != nil { log.Println(err) continue } ocspResp, err := ocsp.ParseResponse(resp, peerCerts[1]) if err != nil { log.Println(err) continue } fmt.Println("ProducedAt:", ocspResp.ProducedAt) fmt.Println("NextUpdate:", ocspResp.NextUpdate) fmt.Println("Delta:", ocspResp.NextUpdate.Sub(ocspResp.ProducedAt)) } }
// sendOCSPRequest attempts to request an OCSP response from the // server. The error only indicates a failure to *fetch* the // certificate, and *does not* mean the certificate is valid. func sendOCSPRequest(server string, req []byte, issuer *x509.Certificate) (*ocsp.Response, error) { var resp *http.Response var err error if len(req) > 256 { buf := bytes.NewBuffer(req) resp, err = http.Post(server, "application/ocsp-request", buf) } else { reqURL := server + "/" + base64.StdEncoding.EncodeToString(req) resp, err = http.Get(reqURL) } if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, errors.New("failed to retrieve OSCP") } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } resp.Body.Close() switch { case bytes.Equal(body, ocsp.UnauthorizedErrorResponse): return nil, errors.New("OSCP unauthorized") case bytes.Equal(body, ocsp.MalformedRequestErrorResponse): return nil, errors.New("OSCP malformed") case bytes.Equal(body, ocsp.InternalErrorErrorResponse): return nil, errors.New("OSCP internal error") case bytes.Equal(body, ocsp.TryLaterErrorResponse): return nil, errors.New("OSCP try later") case bytes.Equal(body, ocsp.SigRequredErrorResponse): return nil, errors.New("OSCP signature required") } return ocsp.ParseResponse(body, issuer) }
// Read reads a OCSP response from disk func (dc *DiskCache) Read(name string, serial *big.Int, issuer *x509.Certificate) (*ocsp.Response, []byte) { name = path.Join(dc.path, name) + ".resp" response, err := ioutil.ReadFile(name) if err != nil && !os.IsNotExist(err) { dc.failer.Fail(dc.logger, fmt.Sprintf("[disk-cache] Failed to read response from '%s': %s", name, err)) return nil, nil } else if err != nil { return nil, nil // no file exists yet } parsed, err := ocsp.ParseResponse(response, issuer) if err != nil { dc.failer.Fail(dc.logger, fmt.Sprintf("[disk-cache] Failed to parse response from '%s': %s", name, err)) return nil, nil } err = stapledOCSP.VerifyResponse(dc.clk.Now(), serial, parsed) if err != nil { dc.failer.Fail(dc.logger, fmt.Sprintf("[disk-cache] Failed to verify response from '%s': %s", name, err)) return nil, nil } dc.logger.Info("[disk-cache] Loaded valid response from '%s'", name) return parsed, response }
// GetOCSPInfo is used to get OCSP response details from []byte containing the response. The // OCSP bytes input needs to be a DER encoded OCSP response. An OCSPInfo struct is returned // unless an error is encountered and then a non-nil error is returned. func GetOCSPInfo(ocspBytes []byte) (OCSPInfo, error) { // TODO: Add issuer so the signature is validated ocspInfo := new(OCSPInfo) ocspResp, err := ocsp.ParseResponse(ocspBytes, nil) if err != nil { return *ocspInfo, err } ocspInfo.Serial = ocspResp.SerialNumber ocspInfo.ThisUpdate = ocspResp.ThisUpdate ocspInfo.NextUpdate = ocspResp.NextUpdate switch ocspResp.Status { case ocsp.Good: ocspInfo.Status = "Good" case ocsp.Revoked: ocspInfo.Status = "Revoked" case ocsp.Unknown: ocspInfo.Status = "Unknown" } return *ocspInfo, err }
func (OCSPC *OCSPCert) updateStaple() (err error) { var resp *http.Response for i := 0; i < len(OCSPC.cert.Leaf.OCSPServer); i++ { req, err := http.NewRequest("GET", OCSPC.cert.Leaf.OCSPServer[i]+"/"+base64.StdEncoding.EncodeToString(OCSPC.req), nil) req.Header.Add("Content-Language", "application/ocsp-request") req.Header.Add("Accept", "application/ocsp-response") resp, err = http.DefaultClient.Do(req) if err == nil { break } if i == len(OCSPC.cert.Leaf.OCSPServer) { break } } var OCSPStaple []byte if OCSPStaple, err = ioutil.ReadAll(resp.Body); err != nil { return err } OCSPResp, _ := ocsp.ParseResponse(OCSPStaple, OCSPC.issuer) if OCSPResp.NextUpdate != (time.Time{}) { OCSPC.nextUpdate = OCSPResp.NextUpdate } else { OCSPC.nextUpdate = time.Now().Add(time.Second * OCSP_INTERVAL) } cert := *OCSPC.cert cert.OCSPStaple = OCSPStaple OCSPC.Lock() OCSPC.cert = &cert OCSPC.Unlock() resp.Body.Close() if err == nil { log.Println("successfully fetched OCSP reponse and stapled") log.Println("next update at", OCSPC.nextUpdate) } return err }
// stapleOCSP staples OCSP information to cert for hostname name. // If you have it handy, you should pass in the PEM-encoded certificate // bundle; otherwise the DER-encoded cert will have to be PEM-encoded. // If you don't have the PEM blocks already, just pass in nil. // // Errors here are not necessarily fatal, it could just be that the // certificate doesn't have an issuer URL. func stapleOCSP(cert *Certificate, pemBundle []byte) error { if pemBundle == nil { // The function in the acme package that gets OCSP requires a PEM-encoded cert bundle := new(bytes.Buffer) for _, derBytes := range cert.Certificate.Certificate { pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) } pemBundle = bundle.Bytes() } var ocspBytes []byte var ocspResp *ocsp.Response var ocspErr error var gotNewOCSP bool // First try to load OCSP staple from storage and see if // we can still use it. // TODO: Use Storage interface instead of disk directly var ocspFileNamePrefix string if len(cert.Names) > 0 { ocspFileNamePrefix = cert.Names[0] + "-" } ocspFileName := ocspFileNamePrefix + fastHash(pemBundle) ocspCachePath := filepath.Join(ocspFolder, ocspFileName) cachedOCSP, err := ioutil.ReadFile(ocspCachePath) if err == nil { resp, err := ocsp.ParseResponse(cachedOCSP, nil) if err == nil { if freshOCSP(resp) { // staple is still fresh; use it ocspBytes = cachedOCSP ocspResp = resp } } else { // invalid contents; delete the file // (we do this independently of the maintenance routine because // in this case we know for sure this should be a staple file // because we loaded it by name, whereas the maintenance routine // just iterates the list of files, even if somehow a non-staple // file gets in the folder. in this case we are sure it is corrupt.) err := os.Remove(ocspCachePath) if err != nil { log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err) } } } // If we couldn't get a fresh staple by reading the cache, // then we need to request it from the OCSP responder if ocspResp == nil || len(ocspBytes) == 0 { ocspBytes, ocspResp, ocspErr = acme.GetOCSPForCert(pemBundle) if ocspErr != nil { // An error here is not a problem because a certificate may simply // not contain a link to an OCSP server. But we should log it anyway. // There's nothing else we can do to get OCSP for this certificate, // so we can return here with the error. return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr) } gotNewOCSP = true } // By now, we should have a response. If good, staple it to // the certificate. If the OCSP response was not loaded from // storage, we persist it for next time. if ocspResp.Status == ocsp.Good { cert.Certificate.OCSPStaple = ocspBytes cert.OCSP = ocspResp if gotNewOCSP { err := os.MkdirAll(filepath.Join(caddy.AssetsPath(), "ocsp"), 0700) if err != nil { return fmt.Errorf("unable to make OCSP staple path for %v: %v", cert.Names, err) } err = ioutil.WriteFile(ocspCachePath, ocspBytes, 0644) if err != nil { return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err) } } } return nil }
func TestOCSPRefreshMain(t *testing.T) { db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db") certPEM, err := ioutil.ReadFile("../../ocsp/testdata/cert.pem") if err != nil { t.Fatal(err) } expirationTime := time.Now().AddDate(1, 0, 0) var cert = &certdb.CertificateRecord{ Serial: "1333308112180215502", // from cert.pem Expiry: expirationTime, PEM: string(certPEM), Status: "good", } err = certdb.InsertCertificate(db, cert) if err != nil { t.Fatal(err) } err = ocsprefreshMain([]string{}, cli.Config{ CAFile: "../../ocsp/testdata/ca.pem", ResponderFile: "../../ocsp/testdata/server.crt", ResponderKeyFile: "../../ocsp/testdata/server.key", DBConfigFile: "../testdata/db-config.json", Interval: helpers.OneDay, }) if err != nil { t.Fatal(err) } var records []*certdb.OCSPRecord records, err = certdb.GetUnexpiredOCSPs(db) if err != nil { t.Fatal("Failed to get OCSP responses") } if len(records) != 1 { t.Fatal("Expected one OCSP response") } var resp *ocsp.Response resp, err = ocsp.ParseResponse([]byte(records[0].Body), nil) if err != nil { t.Fatal("Failed to parse OCSP response") } if resp.Status != ocsp.Good { t.Fatal("Expected cert status 'good'") } err = certdb.RevokeCertificate(db, cert.Serial, ocsp.KeyCompromise) if err != nil { t.Fatal("Failed to revoke certificate") } err = ocsprefreshMain([]string{}, cli.Config{ CAFile: "../../ocsp/testdata/ca.pem", ResponderFile: "../../ocsp/testdata/server.crt", ResponderKeyFile: "../../ocsp/testdata/server.key", DBConfigFile: "../testdata/db-config.json", Interval: helpers.OneDay, }) if err != nil { t.Fatal(err) } records, err = certdb.GetUnexpiredOCSPs(db) if err != nil { t.Fatal("Failed to get OCSP responses") } if len(records) != 1 { t.Fatal("Expected one OCSP response") } resp, err = ocsp.ParseResponse([]byte(records[0].Body), nil) if err != nil { t.Fatal("Failed to parse OCSP response") } if resp.Status != ocsp.Revoked { t.Fatal("Expected cert status 'revoked'") } }
func TestSign(t *testing.T) { for i, test := range signTests { resp, body := testSignFile(t, test.CertificateFile, test.Status, test.Reason, test.RevokedAt) if resp.StatusCode != test.ExpectedHTTPStatus { t.Logf("Test %d: expected: %d, have %d", i, test.ExpectedHTTPStatus, resp.StatusCode) t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body)) } message := new(api.Response) err := json.Unmarshal(body, message) if err != nil { t.Logf("failed to read response body: %v", err) t.Fatal(resp.Status, test.ExpectedHTTPStatus, message) } if test.ExpectedSuccess != message.Success { t.Logf("Test %d: expected: %v, have %v", i, test.ExpectedSuccess, message.Success) t.Fatal(resp.Status, test.ExpectedHTTPStatus, message) } if !test.ExpectedSuccess { if test.ExpectedErrorCode != message.Errors[0].Code { t.Fatalf("Test %d: expected: %v, have %v", i, test.ExpectedErrorCode, message.Errors[0].Code) t.Fatal(resp.Status, test.ExpectedHTTPStatus, message) } continue } result, ok := message.Result.(map[string]interface{}) if !ok { t.Logf("failed to read result") t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body)) } b64Resp, ok := result["ocspResponse"].(string) if !ok { t.Logf("failed to find ocspResponse") t.Fatal(resp.Status, test.ExpectedHTTPStatus, string(body)) } der, err := base64.StdEncoding.DecodeString(b64Resp) if err != nil { t.Logf("failed to decode base64") t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp) } ocspResp, err := goocsp.ParseResponse(der, nil) if err != nil { t.Logf("failed to parse ocsp response: %v", err) t.Fatal(resp.Status, test.ExpectedHTTPStatus, b64Resp) } //should default to good if test.Status == "" { test.Status = "good" } intStatus := ocsp.StatusCode[test.Status] if ocspResp.Status != intStatus { t.Fatalf("Test %d incorrect status: expected: %v, have %v", i, intStatus, ocspResp.Status) t.Fatal(ocspResp.Status, intStatus, ocspResp) } if test.Status == "revoked" { if ocspResp.RevocationReason != test.Reason { t.Fatalf("Test %d incorrect reason: expected: %v, have %v", i, test.Reason, ocspResp.RevocationReason) t.Fatal(ocspResp.RevocationReason, test.Reason, ocspResp) } var r time.Time if test.RevokedAt == "" || test.RevokedAt == "now" { r = time.Now() } else { r, _ = time.Parse("2006-01-02", test.RevokedAt) } if ocspResp.RevokedAt.Year() != r.Year() { t.Fatalf("Test %d incorrect revokedAt: expected: %v, have %v", i, test.RevokedAt, ocspResp.RevokedAt) t.Fatal(ocspResp.RevokedAt, test.RevokedAt, ocspResp) } if ocspResp.RevokedAt.Month() != r.Month() { t.Fatalf("Test %d incorrect revokedAt: expected: %v, have %v", i, test.RevokedAt, ocspResp.RevokedAt) t.Fatal(ocspResp.RevokedAt, test.RevokedAt, ocspResp) } if ocspResp.RevokedAt.Day() != r.Day() { t.Fatalf("Test %d incorrect revokedAt: expected: %v, have %v", i, test.RevokedAt, ocspResp.RevokedAt) t.Fatal(ocspResp.RevokedAt, test.RevokedAt, ocspResp) } } } }
// GetOCSPForCert takes a PEM encoded cert or cert bundle returning the raw OCSP response, // the parsed response, and an error, if any. The returned []byte can be passed directly // into the OCSPStaple property of a tls.Certificate. If the bundle only contains the // issued certificate, this function will try to get the issuer certificate from the // IssuingCertificateURL in the certificate. If the []byte and/or ocsp.Response return // values are nil, the OCSP status may be assumed OCSPUnknown. func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) { certificates, err := parsePEMBundle(bundle) if err != nil { return nil, nil, err } // We expect the certificate slice to be ordered downwards the chain. // SRV CRT -> CA. We need to pull the leaf and issuer certs out of it, // which should always be the first two certificates. If there's no // OCSP server listed in the leaf cert, there's nothing to do. And if // we have only one certificate so far, we need to get the issuer cert. issuedCert := certificates[0] if len(issuedCert.OCSPServer) == 0 { return nil, nil, errors.New("no OCSP server specified in cert") } if len(certificates) == 1 { // TODO: build fallback. If this fails, check the remaining array entries. if len(issuedCert.IssuingCertificateURL) == 0 { return nil, nil, errors.New("no issuing certificate URL") } resp, err := httpGet(issuedCert.IssuingCertificateURL[0]) if err != nil { return nil, nil, err } defer resp.Body.Close() issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, 1024*1024)) if err != nil { return nil, nil, err } issuerCert, err := x509.ParseCertificate(issuerBytes) if err != nil { return nil, nil, err } // Insert it into the slice on position 0 // We want it ordered right SRV CRT -> CA certificates = append(certificates, issuerCert) } issuerCert := certificates[1] // Finally kick off the OCSP request. ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil) if err != nil { return nil, nil, err } reader := bytes.NewReader(ocspReq) req, err := httpPost(issuedCert.OCSPServer[0], "application/ocsp-request", reader) if err != nil { return nil, nil, err } defer req.Body.Close() ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024)) ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert) if err != nil { return nil, nil, err } if ocspRes.Certificate == nil { err = ocspRes.CheckSignatureFrom(issuerCert) if err != nil { return nil, nil, err } } return ocspResBytes, ocspRes, nil }
func main() { flag.Parse() if logURL == nil || logKey == nil || fileName == nil { flag.PrintDefaults() return } statuses[ocsp.Good] = "good" statuses[ocsp.Revoked] = "revoked" statuses[ocsp.Unknown] = "unknown" statuses[ocsp.ServerFailed] = "fail" pemPublicKey := fmt.Sprintf(`-----BEGIN PUBLIC KEY----- %s -----END PUBLIC KEY-----`, *logKey) ctLog, err := certificatetransparency.NewLog(*logURL, pemPublicKey) if err != nil { fmt.Fprintf(os.Stderr, "Failed to initialize log: %s\n", err) os.Exit(1) } file, err := os.OpenFile(*fileName, os.O_RDWR|os.O_CREATE, 0666) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open entries file: %s\n", err) os.Exit(1) } defer file.Close() entriesFile := certificatetransparency.EntriesFile{file} if !*skipUpdate { sth, err := ctLog.GetSignedTreeHead() if err != nil { fmt.Fprintf(os.Stderr, "GetSignedTreeHead: %s\n", err) os.Exit(1) } fmt.Printf("%d total entries at %s\n", sth.Size, sth.Time.Format(time.ANSIC)) count, err := entriesFile.Count() if err != nil { fmt.Fprintf(os.Stderr, "\nFailed to read entries file: %s\n", err) os.Exit(1) } if count < sth.Size { _, err = ctLog.DownloadRange(file, nil, count, sth.Size) if err != nil { fmt.Fprintf(os.Stderr, "\nFailed to update CT log: %s\n", err) os.Exit(1) } } entriesFile.Seek(0, 0) treeHash, err := entriesFile.HashTree(nil, sth.Size) if err != nil { fmt.Fprintf(os.Stderr, "Error hashing tree: %s", err) os.Exit(1) } if !bytes.Equal(treeHash[:], sth.Hash) { fmt.Fprintf(os.Stderr, "Hashes do not match! Calculated: %x, STH contains %x\n", treeHash, sth.Hash) os.Exit(1) } fmt.Println("Hashes match! Calculated: %s, STH contains %s\n", hex.EncodeToString(treeHash[:]), hex.EncodeToString(sth.Hash[:])) entriesFile.Seek(0, 0) } dataChan := make(chan data) go func() { entriesFile.Map(func(ent *certificatetransparency.EntryAndPosition, err error) { if err != nil { return } cert, err := x509.ParseCertificate(ent.Entry.X509Cert) if err != nil { return } if cert.Issuer.CommonName != "Let's Encrypt Authority X1" { return } if time.Now().After(cert.NotAfter) { return } var issuer *x509.Certificate if len(ent.Entry.ExtraCerts) > 0 { issuer, err = x509.ParseCertificate(ent.Entry.ExtraCerts[0]) if err != nil { fmt.Fprintf(os.Stderr, "Failed to parse issuer: %s\n", err) return } } if len(cert.OCSPServer) == 0 { if cert.Issuer.CommonName != "Merge Delay Intermediate 1" { fmt.Fprintf(os.Stderr, "No OCSP Server for %s\n", cert.Issuer.CommonName) } return } ocspServer := cert.OCSPServer[0] req, err := ocsp.CreateRequest(cert, issuer, &ocsp.RequestOptions{}) if err != nil { fmt.Fprintf(os.Stderr, "Error creating OCSP request: %s\n", err) return } url := fmt.Sprintf("%s%s", ocspServer, base64.StdEncoding.EncodeToString(req)) start := time.Now() httpResponse, err := http.Post(ocspServer, "application/ocsp-request", bytes.NewBuffer(req)) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching OCSP: %s %s\n", err, url) return } defer httpResponse.Body.Close() datum := data{ serial: fmt.Sprintf("%032x", cert.SerialNumber), names: cert.DNSNames, ocspLatency: time.Now().Sub(start), notBefore: cert.NotBefore, url: url, } if datum.ocspLatency > time.Second { fmt.Printf("slow response (%dms) for %x: %s\n", datum.ocspLatency/time.Millisecond, cert.SerialNumber, url) } names := strings.Join(cert.DNSNames, ", ") if err != nil { datum.ocspErr = fmt.Errorf("error fetching OCSP for %s %s: %s\n", names, url, err) dataChan <- datum return } ocspResponse, err := ioutil.ReadAll(httpResponse.Body) if err != nil { datum.ocspErr = fmt.Errorf("error reading OCSP for %s %s: %s\n", names, url, err) dataChan <- datum return } parsedResponse, err := ocsp.ParseResponse(ocspResponse, issuer) if err != nil { datum.ocspErr = fmt.Errorf("error parsing OCSP response for %s %s: %s\n", names, url, err) dataChan <- datum return } datum.nextUpdate = parsedResponse.NextUpdate datum.thisUpdate = parsedResponse.ThisUpdate dataChan <- datum }) close(dataChan) }() processData(dataChan) }
func TestOCSP(t *testing.T) { testCtx := setup(t) ca, err := NewCertificateAuthorityImpl( testCtx.caConfig, testCtx.fc, testCtx.stats, testCtx.issuers, testCtx.keyPolicy) test.AssertNotError(t, err, "Failed to create CA") ca.Publisher = &mocks.Publisher{} ca.PA = testCtx.pa ca.SA = &mockSA{} csr, _ := x509.ParseCertificateRequest(CNandSANCSR) cert, err := ca.IssueCertificate(ctx, *csr, 1001) test.AssertNotError(t, err, "Failed to issue") parsedCert, err := x509.ParseCertificate(cert.DER) test.AssertNotError(t, err, "Failed to parse cert") ocspResp, err := ca.GenerateOCSP(ctx, core.OCSPSigningRequest{ CertDER: cert.DER, Status: string(core.OCSPStatusGood), }) test.AssertNotError(t, err, "Failed to generate OCSP") parsed, err := ocsp.ParseResponse(ocspResp, caCert) test.AssertNotError(t, err, "Failed to parse validate OCSP") test.AssertEquals(t, parsed.Status, 0) test.AssertEquals(t, parsed.RevocationReason, 0) test.AssertEquals(t, parsed.SerialNumber.Cmp(parsedCert.SerialNumber), 0) // Test that signatures are checked. ocspResp, err = ca.GenerateOCSP(ctx, core.OCSPSigningRequest{ CertDER: append(cert.DER, byte(0)), Status: string(core.OCSPStatusGood), }) test.AssertError(t, err, "Generated OCSP for cert with bad signature") // Load multiple issuers, including the old issuer, and ensure OCSP is still // signed correctly. newIssuerCert, err := core.LoadCert("../test/test-ca2.pem") test.AssertNotError(t, err, "Failed to load new cert") newIssuers := []Issuer{ { Signer: caKey, // newIssuerCert is first, so it will be the default. Cert: newIssuerCert, }, { Signer: caKey, Cert: caCert, }, } ca, err = NewCertificateAuthorityImpl( testCtx.caConfig, testCtx.fc, testCtx.stats, newIssuers, testCtx.keyPolicy) test.AssertNotError(t, err, "Failed to remake CA") ca.Publisher = &mocks.Publisher{} ca.PA = testCtx.pa ca.SA = &mockSA{} // Now issue a new cert, signed by newIssuerCert newCert, err := ca.IssueCertificate(ctx, *csr, 1001) test.AssertNotError(t, err, "Failed to issue newCert") parsedNewCert, err := x509.ParseCertificate(newCert.DER) test.AssertNotError(t, err, "Failed to parse newCert") err = parsedNewCert.CheckSignatureFrom(newIssuerCert) t.Logf("check sig: %s", err) // ocspResp2 is a second OCSP response for `cert` (issued by caCert), and // should be signed by caCert. ocspResp2, err := ca.GenerateOCSP(ctx, core.OCSPSigningRequest{ CertDER: append(cert.DER), Status: string(core.OCSPStatusGood), }) test.AssertNotError(t, err, "Failed to sign second OCSP response") _, err = ocsp.ParseResponse(ocspResp2, caCert) test.AssertNotError(t, err, "Failed to parse / validate second OCSP response") // newCertOcspResp is an OCSP response for `newCert` (issued by newIssuer), // and should be signed by newIssuer. newCertOcspResp, err := ca.GenerateOCSP(ctx, core.OCSPSigningRequest{ CertDER: newCert.DER, Status: string(core.OCSPStatusGood), }) test.AssertNotError(t, err, "Failed to generate OCSP") parsedNewCertOcspResp, err := ocsp.ParseResponse(newCertOcspResp, newIssuerCert) test.AssertNotError(t, err, "Failed to parse / validate OCSP for newCert") test.AssertEquals(t, parsedNewCertOcspResp.Status, 0) test.AssertEquals(t, parsedNewCertOcspResp.RevocationReason, 0) test.AssertEquals(t, parsedNewCertOcspResp.SerialNumber.Cmp(parsedNewCert.SerialNumber), 0) }
func TestOCSPRefreshMain(t *testing.T) { db := testdb.SQLiteDB("../../certdb/testdb/certstore_development.db") certPEM, err := ioutil.ReadFile("../../ocsp/testdata/cert.pem") if err != nil { t.Fatal(err) } cert, err := helpers.ParseCertificatePEM(certPEM) if err != nil { t.Fatal(err) } expirationTime := time.Now().AddDate(1, 0, 0) certRecord := certdb.CertificateRecord{ Serial: cert.SerialNumber.String(), AKI: hex.EncodeToString(cert.AuthorityKeyId), Expiry: expirationTime, PEM: string(certPEM), Status: "good", } dbAccessor = sql.NewAccessor(db) err = dbAccessor.InsertCertificate(certRecord) if err != nil { t.Fatal(err) } err = ocsprefreshMain([]string{}, cli.Config{ CAFile: "../../ocsp/testdata/ca.pem", ResponderFile: "../../ocsp/testdata/server.crt", ResponderKeyFile: "../../ocsp/testdata/server.key", DBConfigFile: "../testdata/db-config.json", Interval: helpers.OneDay, }) if err != nil { t.Fatal(err) } records, err := dbAccessor.GetUnexpiredOCSPs() if err != nil { t.Fatal("Failed to get OCSP responses") } if len(records) != 1 { t.Fatal("Expected one OCSP response") } var resp *ocsp.Response resp, err = ocsp.ParseResponse([]byte(records[0].Body), nil) if err != nil { t.Fatal("Failed to parse OCSP response") } if resp.Status != ocsp.Good { t.Fatal("Expected cert status 'good'") } err = dbAccessor.RevokeCertificate(certRecord.Serial, certRecord.AKI, ocsp.KeyCompromise) if err != nil { t.Fatal("Failed to revoke certificate") } err = ocsprefreshMain([]string{}, cli.Config{ CAFile: "../../ocsp/testdata/ca.pem", ResponderFile: "../../ocsp/testdata/server.crt", ResponderKeyFile: "../../ocsp/testdata/server.key", DBConfigFile: "../testdata/db-config.json", Interval: helpers.OneDay, }) if err != nil { t.Fatal(err) } records, err = dbAccessor.GetUnexpiredOCSPs() if err != nil { t.Fatal("Failed to get OCSP responses") } if len(records) != 1 { t.Fatal("Expected one OCSP response") } resp, err = ocsp.ParseResponse([]byte(records[0].Body), nil) if err != nil { t.Fatal("Failed to parse OCSP response") } if resp.Status != ocsp.Revoked { t.Fatal("Expected cert status 'revoked'") } }
// A Responder can process both GET and POST requests. The mapping // from an OCSP request to an OCSP response is done by the Source; // the Responder simply decodes the request, and passes back whatever // response is provided by the source. // Note: The caller must use http.StripPrefix to strip any path components // (including '/') on GET requests. // Do not use this responder in conjunction with http.NewServeMux, because the // default handler will try to canonicalize path components by changing any // strings of repeated '/' into a single '/', which will break the base64 // encoding. func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) { // By default we set a 'max-age=0, no-cache' Cache-Control header, this // is only returned to the client if a valid authorized OCSP response // is not found or an error is returned. If a response if found the header // will be altered to contain the proper max-age and modifiers. response.Header().Add("Cache-Control", "max-age=0, no-cache") // Read response from request var requestBody []byte var err error switch request.Method { case "GET": base64Request, err := url.QueryUnescape(request.URL.Path) if err != nil { log.Infof("Error decoding URL: %s", request.URL.Path) response.WriteHeader(http.StatusBadRequest) return } // url.QueryUnescape not only unescapes %2B escaping, but it additionally // turns the resulting '+' into a space, which makes base64 decoding fail. // So we go back afterwards and turn ' ' back into '+'. This means we // accept some malformed input that includes ' ' or %20, but that's fine. base64RequestBytes := []byte(base64Request) for i := range base64RequestBytes { if base64RequestBytes[i] == ' ' { base64RequestBytes[i] = '+' } } requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes)) if err != nil { log.Infof("Error decoding base64 from URL: %s", base64Request) response.WriteHeader(http.StatusBadRequest) return } case "POST": requestBody, err = ioutil.ReadAll(request.Body) if err != nil { log.Errorf("Problem reading body of POST: %s", err) response.WriteHeader(http.StatusBadRequest) return } default: response.WriteHeader(http.StatusMethodNotAllowed) return } b64Body := base64.StdEncoding.EncodeToString(requestBody) log.Debugf("Received OCSP request: %s", b64Body) // All responses after this point will be OCSP. // We could check for the content type of the request, but that // seems unnecessariliy restrictive. response.Header().Add("Content-Type", "application/ocsp-response") // Parse response as an OCSP request // XXX: This fails if the request contains the nonce extension. // We don't intend to support nonces anyway, but maybe we // should return unauthorizedRequest instead of malformed. ocspRequest, err := ocsp.ParseRequest(requestBody) if err != nil { log.Infof("Error decoding request body: %s", b64Body) response.WriteHeader(http.StatusBadRequest) response.Write(malformedRequestErrorResponse) return } // Look up OCSP response from source ocspResponse, found := rs.Source.Response(ocspRequest) if !found { log.Infof("No response found for request: serial %x, request body %s", ocspRequest.SerialNumber, b64Body) response.Write(unauthorizedErrorResponse) return } parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil) if err != nil { log.Errorf("Error parsing response for serial %x: %s", ocspRequest.SerialNumber, err) response.Write(unauthorizedErrorResponse) return } // Write OCSP response to response response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123)) response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123)) now := rs.clk.Now() maxAge := 0 if now.Before(parsedResponse.NextUpdate) { maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second) } else { // TODO(#530): we want max-age=0 but this is technically an authorized OCSP response // (despite being stale) and 5019 forbids attaching no-cache maxAge = 0 } response.Header().Set( "Cache-Control", fmt.Sprintf( "max-age=%d, public, no-transform, must-revalidate", maxAge, ), ) responseHash := sha256.Sum256(ocspResponse) response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash)) // RFC 7232 says that a 304 response must contain the above // headers if they would also be sent for a 200 for the same // request, so we have to wait until here to do this if etag := request.Header.Get("If-None-Match"); etag != "" { if etag == fmt.Sprintf("\"%X\"", responseHash) { response.WriteHeader(http.StatusNotModified) return } } response.WriteHeader(http.StatusOK) response.Write(ocspResponse) }
// A Responder can process both GET and POST requests. The mapping // from an OCSP request to an OCSP response is done by the Source; // the Responder simply decodes the request, and passes back whatever // response is provided by the source. // Note: The caller must use http.StripPrefix to strip any path components // (including '/') on GET requests. // Do not use this responder in conjunction with http.NewServeMux, because the // default handler will try to canonicalize path components by changing any // strings of repeated '/' into a single '/', which will break the base64 // encoding. func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) { // Read response from request var requestBody []byte var err error switch request.Method { case "GET": base64Request, err := url.QueryUnescape(request.URL.Path) if err != nil { log.Errorf("Error decoding URL: %s", request.URL.Path) response.WriteHeader(http.StatusBadRequest) return } // url.QueryUnescape not only unescapes %2B escaping, but it additionally // turns the resulting '+' into a space, which makes base64 decoding fail. // So we go back afterwards and turn ' ' back into '+'. This means we // accept some malformed input that includes ' ' or %20, but that's fine. base64RequestBytes := []byte(base64Request) for i := range base64RequestBytes { if base64RequestBytes[i] == ' ' { base64RequestBytes[i] = '+' } } requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes)) if err != nil { log.Errorf("Error decoding base64 from URL: %s", base64Request) response.WriteHeader(http.StatusBadRequest) return } case "POST": requestBody, err = ioutil.ReadAll(request.Body) if err != nil { log.Errorf("Problem reading body of POST: %s", err) response.WriteHeader(http.StatusBadRequest) return } default: response.WriteHeader(http.StatusMethodNotAllowed) return } // TODO log request b64Body := base64.StdEncoding.EncodeToString(requestBody) log.Infof("Received OCSP request: %s", b64Body) // All responses after this point will be OCSP. // We could check for the content type of the request, but that // seems unnecessariliy restrictive. response.Header().Add("Content-Type", "application/ocsp-response") // Parse response as an OCSP request // XXX: This fails if the request contains the nonce extension. // We don't intend to support nonces anyway, but maybe we // should return unauthorizedRequest instead of malformed. ocspRequest, err := ocsp.ParseRequest(requestBody) if err != nil { log.Errorf("Error decoding request body: %s", b64Body) response.WriteHeader(http.StatusBadRequest) response.Write(malformedRequestErrorResponse) return } // Look up OCSP response from source ocspResponse, found := rs.Source.Response(ocspRequest) if !found { log.Errorf("No response found for request: %s", b64Body) response.Write(unauthorizedErrorResponse) return } parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil) if err != nil { log.Errorf("Error parsing response: %s", err) response.Write(unauthorizedErrorResponse) return } // Write OCSP response to response response.Header().Add("Last-Modified", parsedResponse.ProducedAt.Format(time.RFC1123)) response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123)) maxAge := int64(parsedResponse.NextUpdate.Sub(rs.clk.Now()) / time.Second) if maxAge > 0 { response.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d", maxAge)) } response.WriteHeader(http.StatusOK) response.Write(ocspResponse) }
// TODO Security Issue : this code was audited 0 time func (db *HTTPDB) DialerTLS(network, addr string) (conn net.Conn, err error) { var ocsprep *ocsp.Response certok := false hostok := false ocspok := false c, err := tls.Dial(network, addr, db.tlsconfig) if err != nil { return c, err } cstate := c.ConnectionState() if cstate.OCSPResponse != nil { ocsprep, err = ocsp.ParseResponse(cstate.OCSPResponse, nil) if err != nil { return nil, err } switch ocsprep.Status { case ocsp.Good, ocsp.Unknown: default: return nil, errors.New(fmt.Sprintf("invalid OCSP")) } } for _, peercert := range cstate.PeerCertificates { der, err := x509.MarshalPKIXPublicKey(peercert.PublicKey) if err != nil { return nil, err } if !hostok && peercert.VerifyHostname(db.sni) == nil { hostok = true } if ocsprep != nil && !ocspok && ocsprep.CheckSignatureFrom(peercert) == nil { ocspok = true } rawhash := sha256.Sum256(der) hash := base64.StdEncoding.EncodeToString(rawhash[:]) if valid, ok := db.hpkp[hash]; !certok && ok && valid { certok = true } } if len(db.hpkp) > 0 && !certok { return nil, errors.New(fmt.Sprintf("invalid HPKP")) } if !hostok { return nil, errors.New(fmt.Sprintf("invalid SNI")) } if ocsprep != nil && !ocspok { return nil, errors.New(fmt.Sprintf("invalid OCSP")) } return c, nil }