Пример #1
0
// 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
}
Пример #2
0
// 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)
}
Пример #3
0
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
}
Пример #4
0
// 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)
			}
		}
	}
}
Пример #5
0
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")
}
Пример #6
0
// 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
}
Пример #7
0
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)
	}
}
Пример #8
0
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
}
Пример #9
0
// 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)
}
Пример #10
0
// 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
}
Пример #11
0
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")
	}
}
Пример #12
0
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))
	}
}
Пример #13
0
// 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)
}
Пример #14
0
// 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
}
Пример #15
0
// 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
}
Пример #16
0
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
}
Пример #17
0
// 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
}
Пример #18
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)
	}

	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'")
	}
}
Пример #19
0
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)
			}
		}
	}
}
Пример #20
0
// 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
}
Пример #21
0
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)
}
Пример #22
0
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'")
	}
}
Пример #24
0
// 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)
}
Пример #25
0
// 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)
}
Пример #26
0
// 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
}