ocsp_helper: allow suppressing output. (#5103)

This adds a configurable output parameter for ocsp_helper, which
defaults to stdout. This allows suppressing the stdout output when using
ocsp_helper in integration tests. That output was making it hard to
see details about failing tests.
This commit is contained in:
Jacob Hoffman-Andrews 2020-09-25 14:03:50 -07:00 committed by GitHub
parent d3f2efcb7f
commit d90a2817c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 28 deletions

View File

@ -3,6 +3,7 @@
package integration package integration
import ( import (
"io/ioutil"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -39,7 +40,7 @@ func TestPrecertificateOCSP(t *testing.T) {
t.Fatalf("couldn't find rejected precert for %q", domain) t.Fatalf("couldn't find rejected precert for %q", domain)
} }
ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good) ocspConfig := ocsp_helper.DefaultConfig.WithExpectStatus(ocsp.Good).WithOutput(ioutil.Discard)
_, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig) _, err = ocsp_helper.ReqDER(cert.Raw, ocspConfig)
if err != nil { if err != nil {
t.Errorf("requesting OCSP for rejected precertificate: %s", err) t.Errorf("requesting OCSP for rejected precertificate: %s", err)

View File

@ -8,9 +8,11 @@ import (
"encoding/pem" "encoding/pem"
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"sync" "sync"
"time" "time"
@ -37,6 +39,7 @@ type Config struct {
ignoreExpiredCerts bool ignoreExpiredCerts bool
expectStatus int expectStatus int
expectReason int expectReason int
output io.Writer
} }
// DefaultConfig is a Config populated with the same defaults as if no // DefaultConfig is a Config populated with the same defaults as if no
@ -49,6 +52,7 @@ var DefaultConfig = Config{
ignoreExpiredCerts: *ignoreExpiredCerts, ignoreExpiredCerts: *ignoreExpiredCerts,
expectStatus: *expectStatus, expectStatus: *expectStatus,
expectReason: *expectReason, expectReason: *expectReason,
output: os.Stdout,
} }
var parseFlagsOnce sync.Once var parseFlagsOnce sync.Once
@ -78,6 +82,14 @@ func (template Config) WithExpectStatus(status int) Config {
return ret return ret
} }
// WithOutput returns a new Config with the given output,
// and all other fields the same as the receiver.
func (template Config) WithOutput(w io.Writer) Config {
ret := template
ret.output = w
return ret
}
func getIssuer(cert *x509.Certificate) (*x509.Certificate, error) { func getIssuer(cert *x509.Certificate) (*x509.Certificate, error) {
if cert == nil { if cert == nil {
return nil, fmt.Errorf("nil certificate") return nil, fmt.Errorf("nil certificate")
@ -187,14 +199,14 @@ func ReqDER(der []byte, config Config) (*ocsp.Response, error) {
return nil, err return nil, err
} }
httpResp, err := sendHTTPRequest(req, ocspURL, config.method, config.hostOverride) httpResp, err := sendHTTPRequest(req, ocspURL, config.method, config.hostOverride, config.output)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmt.Printf("HTTP %d\n", httpResp.StatusCode) fmt.Fprintf(config.output, "HTTP %d\n", httpResp.StatusCode)
for k, v := range httpResp.Header { for k, v := range httpResp.Header {
for _, vv := range v { for _, vv := range v {
fmt.Printf("%s: %s\n", k, vv) fmt.Fprintf(config.output, "%s: %s\n", k, vv)
} }
} }
if httpResp.StatusCode != 200 { if httpResp.StatusCode != 200 {
@ -211,16 +223,22 @@ func ReqDER(der []byte, config Config) (*ocsp.Response, error) {
return parseAndPrint(respBytes, cert, issuer, config) return parseAndPrint(respBytes, cert, issuer, config)
} }
func sendHTTPRequest(req []byte, ocspURL *url.URL, method string, host string) (*http.Response, error) { func sendHTTPRequest(
req []byte,
ocspURL *url.URL,
method string,
host string,
output io.Writer,
) (*http.Response, error) {
encodedReq := base64.StdEncoding.EncodeToString(req) encodedReq := base64.StdEncoding.EncodeToString(req)
var httpRequest *http.Request var httpRequest *http.Request
var err error var err error
if method == "GET" { if method == "GET" {
ocspURL.Path = encodedReq ocspURL.Path = encodedReq
fmt.Printf("Fetching %s\n", ocspURL.String()) fmt.Fprintf(output, "Fetching %s\n", ocspURL.String())
httpRequest, err = http.NewRequest("GET", ocspURL.String(), http.NoBody) httpRequest, err = http.NewRequest("GET", ocspURL.String(), http.NoBody)
} else if method == "POST" { } else if method == "POST" {
fmt.Printf("POSTing request, reproduce with: curl -i --data-binary @- %s < <(base64 -d <<<%s)\n", fmt.Fprintf(output, "POSTing request, reproduce with: curl -i --data-binary @- %s < <(base64 -d <<<%s)\n",
ocspURL, encodedReq) ocspURL, encodedReq)
httpRequest, err = http.NewRequest("POST", ocspURL.String(), bytes.NewBuffer(req)) httpRequest, err = http.NewRequest("POST", ocspURL.String(), bytes.NewBuffer(req))
} else { } else {
@ -259,12 +277,12 @@ func getOCSPURL(cert *x509.Certificate, urlOverride string) (*url.URL, error) {
// checkSignerTimes checks that the OCSP response is within the // checkSignerTimes checks that the OCSP response is within the
// validity window of whichever certificate signed it, and that that // validity window of whichever certificate signed it, and that that
// certificate is currently valid. // certificate is currently valid.
func checkSignerTimes(resp *ocsp.Response, issuer *x509.Certificate) error { func checkSignerTimes(resp *ocsp.Response, issuer *x509.Certificate, output io.Writer) error {
var ocspSigner = issuer var ocspSigner = issuer
if delegatedSigner := resp.Certificate; delegatedSigner != nil { if delegatedSigner := resp.Certificate; delegatedSigner != nil {
ocspSigner = delegatedSigner ocspSigner = delegatedSigner
fmt.Printf("Using delegated OCSP signer from response: %s\n", fmt.Fprintf(output, "Using delegated OCSP signer from response: %s\n",
base64.StdEncoding.EncodeToString(ocspSigner.Raw)) base64.StdEncoding.EncodeToString(ocspSigner.Raw))
} }
@ -287,7 +305,7 @@ func checkSignerTimes(resp *ocsp.Response, issuer *x509.Certificate) error {
} }
func parseAndPrint(respBytes []byte, cert, issuer *x509.Certificate, config Config) (*ocsp.Response, error) { func parseAndPrint(respBytes []byte, cert, issuer *x509.Certificate, config Config) (*ocsp.Response, error) {
fmt.Printf("\nDecoding body: %s\n", base64.StdEncoding.EncodeToString(respBytes)) fmt.Fprintf(config.output, "\nDecoding body: %s\n", base64.StdEncoding.EncodeToString(respBytes))
resp, err := ocsp.ParseResponseForCert(respBytes, cert, issuer) resp, err := ocsp.ParseResponseForCert(respBytes, cert, issuer)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing response: %s", err) return nil, fmt.Errorf("parsing response: %s", err)
@ -306,30 +324,34 @@ func parseAndPrint(respBytes []byte, cert, issuer *x509.Certificate, config Conf
errs = append(errs, fmt.Errorf("NextUpdate is too soon: %s", timeTilExpiry)) errs = append(errs, fmt.Errorf("NextUpdate is too soon: %s", timeTilExpiry))
} }
err = checkSignerTimes(resp, issuer) err = checkSignerTimes(resp, issuer, config.output)
if err != nil { if err != nil {
errs = append(errs, fmt.Errorf("checking signature on delegated signer: %s", err)) errs = append(errs, fmt.Errorf("checking signature on delegated signer: %s", err))
} }
fmt.Print("\n") pr := func(s string, v ...interface{}) {
fmt.Print("Response:\n") fmt.Fprintf(config.output, s, v)
fmt.Printf(" CertStatus %d\n", resp.Status) }
fmt.Printf(" SerialNumber %036x\n", resp.SerialNumber)
fmt.Printf(" ProducedAt %s\n", resp.ProducedAt) pr("\n")
fmt.Printf(" ThisUpdate %s\n", resp.ThisUpdate) pr("Response:\n")
fmt.Printf(" NextUpdate %s\n", resp.NextUpdate) pr(" CertStatus %d\n", resp.Status)
fmt.Printf(" RevokedAt %s\n", resp.RevokedAt) pr(" SerialNumber %036x\n", resp.SerialNumber)
fmt.Printf(" RevocationReason %d\n", resp.RevocationReason) pr(" ProducedAt %s\n", resp.ProducedAt)
fmt.Printf(" SignatureAlgorithm %s\n", resp.SignatureAlgorithm) pr(" ThisUpdate %s\n", resp.ThisUpdate)
fmt.Printf(" Extensions %#v\n", resp.Extensions) pr(" NextUpdate %s\n", resp.NextUpdate)
pr(" RevokedAt %s\n", resp.RevokedAt)
pr(" RevocationReason %d\n", resp.RevocationReason)
pr(" SignatureAlgorithm %s\n", resp.SignatureAlgorithm)
pr(" Extensions %#v\n", resp.Extensions)
if resp.Certificate == nil { if resp.Certificate == nil {
fmt.Print(" Certificate: nil\n") pr(" Certificate: nil\n")
} else { } else {
fmt.Print(" Certificate:\n") pr(" Certificate:\n")
fmt.Printf(" Subject: %s\n", resp.Certificate.Subject) pr(" Subject: %s\n", resp.Certificate.Subject)
fmt.Printf(" Issuer: %s\n", resp.Certificate.Issuer) pr(" Issuer: %s\n", resp.Certificate.Issuer)
fmt.Printf(" NotBefore: %s\n", resp.Certificate.NotBefore) pr(" NotBefore: %s\n", resp.Certificate.NotBefore)
fmt.Printf(" NotAfter: %s\n", resp.Certificate.NotAfter) pr(" NotAfter: %s\n", resp.Certificate.NotAfter)
} }
if len(errs) > 0 { if len(errs) > 0 {