ocsp: Add server-side OCSP processing

The current content of the ocsp module is focused on the processing needed to implement an OCSP client: it only implements request generation and response parsing.

This change adds response generation and request parsing.

Change-Id: Idf6f4e69af504520f2b58340734e45cd92bb3d60
Reviewed-on: https://go-review.googlesource.com/3666
Reviewed-by: Adam Langley <agl@golang.org>
diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go
index 0252b58..d38fe0f 100644
--- a/ocsp/ocsp.go
+++ b/ocsp/ocsp.go
@@ -9,10 +9,15 @@
 
 import (
 	"crypto"
-	_ "crypto/sha1"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha1"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/asn1"
+	"errors"
 	"math/big"
 	"time"
 )
@@ -38,6 +43,21 @@
 	SerialNumber  *big.Int
 }
 
+// https://tools.ietf.org/html/rfc2560#section-4.1.1
+type ocspRequest struct {
+	TBSRequest tbsRequest
+}
+
+type tbsRequest struct {
+	Version       int              `asn1:"explicit,tag:0,default:0"`
+	RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"`
+	RequestList   []request
+}
+
+type request struct {
+	Cert certID
+}
+
 type responseASN1 struct {
 	Status   asn1.Enumerated
 	Response responseBytes `asn1:"explicit,tag:0"`
@@ -58,7 +78,7 @@
 type responseData struct {
 	Raw           asn1.RawContent
 	Version       int              `asn1:"optional,default:1,explicit,tag:0"`
-	RequestorName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
+	ResponderName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
 	KeyHash       []byte           `asn1:"optional,explicit,tag:2"`
 	ProducedAt    time.Time
 	Responses     []singleResponse
@@ -93,38 +113,121 @@
 	oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
 )
 
+var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{
+	crypto.SHA1:   asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}),
+	crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}),
+	crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}),
+	crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}),
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+var signatureAlgorithmDetails = []struct {
+	algo       x509.SignatureAlgorithm
+	oid        asn1.ObjectIdentifier
+	pubKeyAlgo x509.PublicKeyAlgorithm
+	hash       crypto.Hash
+}{
+	{x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
+	{x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
+	{x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
+	{x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
+	{x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
+	{x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
+	{x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
+	{x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
+	{x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
+	{x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
+	{x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
+	{x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
+}
+
+// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below
+func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) {
+	var pubType x509.PublicKeyAlgorithm
+
+	switch pub := pub.(type) {
+	case *rsa.PublicKey:
+		pubType = x509.RSA
+		hashFunc = crypto.SHA256
+		sigAlgo.Algorithm = oidSignatureSHA256WithRSA
+		sigAlgo.Parameters = asn1.RawValue{
+			Tag: 5,
+		}
+
+	case *ecdsa.PublicKey:
+		pubType = x509.ECDSA
+
+		switch pub.Curve {
+		case elliptic.P224(), elliptic.P256():
+			hashFunc = crypto.SHA256
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA256
+		case elliptic.P384():
+			hashFunc = crypto.SHA384
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA384
+		case elliptic.P521():
+			hashFunc = crypto.SHA512
+			sigAlgo.Algorithm = oidSignatureECDSAWithSHA512
+		default:
+			err = errors.New("x509: unknown elliptic curve")
+		}
+
+	default:
+		err = errors.New("x509: only RSA and ECDSA keys supported")
+	}
+
+	if err != nil {
+		return
+	}
+
+	if requestedSigAlgo == 0 {
+		return
+	}
+
+	found := false
+	for _, details := range signatureAlgorithmDetails {
+		if details.algo == requestedSigAlgo {
+			if details.pubKeyAlgo != pubType {
+				err = errors.New("x509: requested SignatureAlgorithm does not match private key type")
+				return
+			}
+			sigAlgo.Algorithm, hashFunc = details.oid, details.hash
+			if hashFunc == 0 {
+				err = errors.New("x509: cannot sign with hash function requested")
+				return
+			}
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		err = errors.New("x509: unknown SignatureAlgorithm")
+	}
+
+	return
+}
+
 // TODO(agl): this is taken from crypto/x509 and so should probably be exported
 // from crypto/x509 or crypto/x509/pkix.
 func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm {
-	switch {
-	case oid.Equal(oidSignatureMD2WithRSA):
-		return x509.MD2WithRSA
-	case oid.Equal(oidSignatureMD5WithRSA):
-		return x509.MD5WithRSA
-	case oid.Equal(oidSignatureSHA1WithRSA):
-		return x509.SHA1WithRSA
-	case oid.Equal(oidSignatureSHA256WithRSA):
-		return x509.SHA256WithRSA
-	case oid.Equal(oidSignatureSHA384WithRSA):
-		return x509.SHA384WithRSA
-	case oid.Equal(oidSignatureSHA512WithRSA):
-		return x509.SHA512WithRSA
-	case oid.Equal(oidSignatureDSAWithSHA1):
-		return x509.DSAWithSHA1
-	case oid.Equal(oidSignatureDSAWithSHA256):
-		return x509.DSAWithSHA256
-	case oid.Equal(oidSignatureECDSAWithSHA1):
-		return x509.ECDSAWithSHA1
-	case oid.Equal(oidSignatureECDSAWithSHA256):
-		return x509.ECDSAWithSHA256
-	case oid.Equal(oidSignatureECDSAWithSHA384):
-		return x509.ECDSAWithSHA384
-	case oid.Equal(oidSignatureECDSAWithSHA512):
-		return x509.ECDSAWithSHA512
+	for _, details := range signatureAlgorithmDetails {
+		if oid.Equal(details.oid) {
+			return details.algo
+		}
 	}
 	return x509.UnknownSignatureAlgorithm
 }
 
+// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form.
+func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash {
+	for hash, oid := range hashOIDs {
+		if oid.Equal(target) {
+			return hash
+		}
+	}
+	return crypto.Hash(0)
+}
+
 // This is the exposed reflection of the internal OCSP structures.
 
 const (
@@ -138,6 +241,14 @@
 	ServerFailed = iota
 )
 
+// Request represents an OCSP request. See RFC 2560.
+type Request struct {
+	HashAlgorithm  crypto.Hash
+	IssuerNameHash []byte
+	IssuerKeyHash  []byte
+	SerialNumber   *big.Int
+}
+
 // Response represents an OCSP response. See RFC 2560.
 type Response struct {
 	// Status is one of {Good, Revoked, Unknown, ServerFailed}
@@ -169,6 +280,37 @@
 	return string(p)
 }
 
+// ParseRequest parses an OCSP request in DER form. It only supports
+// requests for a single certificate. Signed requests are not supported.
+// If a request includes a signature, it will result in a ParseError.
+func ParseRequest(bytes []byte) (*Request, error) {
+	var req ocspRequest
+	rest, err := asn1.Unmarshal(bytes, &req)
+	if err != nil {
+		return nil, err
+	}
+	if len(rest) > 0 {
+		return nil, ParseError("trailing data in OCSP request")
+	}
+
+	if len(req.TBSRequest.RequestList) == 0 {
+		return nil, ParseError("OCSP request contains no request body")
+	}
+	innerRequest := req.TBSRequest.RequestList[0]
+
+	hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm)
+	if hashFunc == crypto.Hash(0) {
+		return nil, ParseError("OCSP request uses unknown hash function")
+	}
+
+	return &Request{
+		HashAlgorithm:  hashFunc,
+		IssuerNameHash: innerRequest.Cert.NameHash,
+		IssuerKeyHash:  innerRequest.Cert.IssuerKeyHash,
+		SerialNumber:   innerRequest.Cert.SerialNumber,
+	}, nil
+}
+
 // ParseResponse parses an OCSP response in DER form. It only supports
 // responses for a single certificate. If the response contains a certificate
 // then the signature over the response is checked. If issuer is not nil then
@@ -255,20 +397,6 @@
 	return ret, nil
 }
 
-// https://tools.ietf.org/html/rfc2560#section-4.1.1
-type ocspRequest struct {
-	TBSRequest tbsRequest
-}
-
-type tbsRequest struct {
-	Version     int `asn1:"explicit,tag:0,default:0"`
-	RequestList []request
-}
-
-type request struct {
-	Cert certID
-}
-
 // RequestOptions contains options for constructing OCSP requests.
 type RequestOptions struct {
 	// Hash contains the hash function that should be used when
@@ -293,16 +421,8 @@
 	// used. I took the following from
 	// http://msdn.microsoft.com/en-us/library/ff635603.aspx
 	var hashOID asn1.ObjectIdentifier
-	switch hashFunc {
-	case crypto.SHA1:
-		hashOID = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26})
-	case crypto.SHA256:
-		hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
-	case crypto.SHA384:
-		hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2})
-	case crypto.SHA512:
-		hashOID = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3})
-	default:
+	hashOID, ok := hashOIDs[hashFunc]
+	if !ok {
 		return nil, x509.ErrUnsupportedAlgorithm
 	}
 
@@ -345,3 +465,109 @@
 		},
 	})
 }
+
+// CreateResponse returns a DER-encoded OCSP response with the specified contents.
+// The fields in the response are populated as follows:
+//
+// The responder cert is used to populate the ResponderName field, and the certificate
+// itself is provided alongside the OCSP response signature.
+//
+// The issuer cert is used to puplate the IssuerNameHash and IssuerKeyHash fields.
+// (SHA-1 is used for the hash function; this is not configurable.)
+//
+// The template is used to populate the SerialNumber, RevocationStatus, RevokedAt,
+// RevocationReason, ThisUpdate, and NextUpdate fields.
+//
+// The ProducedAt date is automatically set to the current date, to the nearest minute.
+func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) {
+	var publicKeyInfo struct {
+		Algorithm pkix.AlgorithmIdentifier
+		PublicKey asn1.BitString
+	}
+	if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil {
+		return nil, err
+	}
+
+	h := sha1.New()
+	h.Write(publicKeyInfo.PublicKey.RightAlign())
+	issuerKeyHash := h.Sum(nil)
+
+	h.Reset()
+	h.Write(issuer.RawSubject)
+	issuerNameHash := h.Sum(nil)
+
+	innerResponse := singleResponse{
+		CertID: certID{
+			HashAlgorithm: pkix.AlgorithmIdentifier{
+				Algorithm:  hashOIDs[crypto.SHA1],
+				Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */},
+			},
+			NameHash:      issuerNameHash,
+			IssuerKeyHash: issuerKeyHash,
+			SerialNumber:  template.SerialNumber,
+		},
+		ThisUpdate: template.ThisUpdate.UTC(),
+		NextUpdate: template.NextUpdate.UTC(),
+	}
+
+	switch template.Status {
+	case Good:
+		innerResponse.Good = true
+	case Unknown:
+		innerResponse.Unknown = true
+	case Revoked:
+		innerResponse.Revoked = revokedInfo{
+			RevocationTime: template.RevokedAt,
+			Reason:         template.RevocationReason,
+		}
+	}
+
+	tbsResponseData := responseData{
+		ResponderName: responderCert.Subject.ToRDNSequence(),
+		ProducedAt:    time.Now().Truncate(time.Minute),
+		Responses:     []singleResponse{innerResponse},
+	}
+
+	tbsResponseDataDER, err := asn1.Marshal(tbsResponseData)
+	if err != nil {
+		return nil, err
+	}
+
+	hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm)
+	if err != nil {
+		return nil, err
+	}
+
+	responseHash := hashFunc.New()
+	responseHash.Write(tbsResponseDataDER)
+	signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc)
+	if err != nil {
+		return nil, err
+	}
+
+	response := basicResponse{
+		TBSResponseData:    tbsResponseData,
+		SignatureAlgorithm: signatureAlgorithm,
+		Signature: asn1.BitString{
+			Bytes:     signature,
+			BitLength: 8 * len(signature),
+		},
+	}
+	if template.Certificate != nil {
+		response.Certificates = []asn1.RawValue{
+			asn1.RawValue{FullBytes: template.Certificate.Raw},
+		}
+	}
+	responseDER, err := asn1.Marshal(response)
+	if err != nil {
+		return nil, err
+	}
+
+	return asn1.Marshal(responseASN1{
+		Status: ocspSuccess,
+		Response: responseBytes{
+			ResponseType: idPKIXOCSPBasic,
+			Response:     responseDER,
+		},
+	})
+}
diff --git a/ocsp/ocsp_test.go b/ocsp/ocsp_test.go
index fcdf6fe..449588c 100644
--- a/ocsp/ocsp_test.go
+++ b/ocsp/ocsp_test.go
@@ -6,7 +6,11 @@
 
 import (
 	"bytes"
+	"crypto"
+	"crypto/sha1"
 	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
 	"encoding/hex"
 	"math/big"
 	"reflect"
@@ -89,9 +93,124 @@
 		t.Fatal(err)
 	}
 
-	expected, _ := hex.DecodeString(ocspRequestHex)
-	if !bytes.Equal(request, expected) {
-		t.Errorf("got %x, wanted %x", request, expected)
+	expectedBytes, _ := hex.DecodeString(ocspRequestHex)
+	if !bytes.Equal(request, expectedBytes) {
+		t.Errorf("request: got %x, wanted %x", request, expectedBytes)
+	}
+
+	decodedRequest, err := ParseRequest(expectedBytes)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if decodedRequest.HashAlgorithm != crypto.SHA1 {
+		t.Errorf("request.HashAlgorithm: got %v, want %v", decodedRequest.HashAlgorithm, crypto.SHA1)
+	}
+
+	var publicKeyInfo struct {
+		Algorithm pkix.AlgorithmIdentifier
+		PublicKey asn1.BitString
+	}
+	_, err = asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	h := sha1.New()
+	h.Write(publicKeyInfo.PublicKey.RightAlign())
+	issuerKeyHash := h.Sum(nil)
+
+	h.Reset()
+	h.Write(issuer.RawSubject)
+	issuerNameHash := h.Sum(nil)
+
+	if got := decodedRequest.IssuerKeyHash; !bytes.Equal(got, issuerKeyHash) {
+		t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerKeyHash)
+	}
+
+	if got := decodedRequest.IssuerNameHash; !bytes.Equal(got, issuerNameHash) {
+		t.Errorf("request.IssuerKeyHash: got %x, want %x", got, issuerNameHash)
+	}
+
+	if got := decodedRequest.SerialNumber; got.Cmp(cert.SerialNumber) != 0 {
+		t.Errorf("request.SerialNumber: got %x, want %x", got, cert.SerialNumber)
+	}
+}
+
+func TestOCSPResponse(t *testing.T) {
+	leafCert, _ := hex.DecodeString(leafCertHex)
+	leaf, err := x509.ParseCertificate(leafCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	issuerCert, _ := hex.DecodeString(issuerCertHex)
+	issuer, err := x509.ParseCertificate(issuerCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	responderCert, _ := hex.DecodeString(responderCertHex)
+	responder, err := x509.ParseCertificate(responderCert)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	responderPrivateKeyDER, _ := hex.DecodeString(responderPrivateKeyHex)
+	responderPrivateKey, err := x509.ParsePKCS1PrivateKey(responderPrivateKeyDER)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	producedAt := time.Now().Truncate(time.Minute)
+	thisUpdate := time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC)
+	nextUpdate := time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC)
+	template := Response{
+		Status:           Revoked,
+		SerialNumber:     leaf.SerialNumber,
+		ThisUpdate:       thisUpdate,
+		NextUpdate:       nextUpdate,
+		RevokedAt:        thisUpdate,
+		RevocationReason: 1, // keyCompromise
+		Certificate:      responder,
+	}
+
+	responseBytes, err := CreateResponse(issuer, responder, template, responderPrivateKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	resp, err := ParseResponse(responseBytes, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(resp.ThisUpdate, template.ThisUpdate) {
+		t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, template.ThisUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.NextUpdate, template.NextUpdate) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.RevokedAt, template.RevokedAt) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.ProducedAt, producedAt) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, template.NextUpdate)
+	}
+
+	if resp.Status != template.Status {
+		t.Errorf("resp.Status: got %d, want %d", resp.Status, template.Status)
+	}
+
+	if resp.SerialNumber.Cmp(template.SerialNumber) != 0 {
+		t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, template.SerialNumber)
+	}
+
+	if resp.RevocationReason != template.RevocationReason {
+		t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, template.RevocationReason)
 	}
 }
 
@@ -272,3 +391,63 @@
 	"f5d93b0ab598fab378b91ef22b4c62d5fdb27a1ddf33fd73f9a5d82d8c2aead1fcb028b6" +
 	"e94948134b838a1b487b24f738de6f4154b8ab576b06dfc7a2d4a9f6f136628088f28b75" +
 	"d68071"
+
+// Key and certificate for the OCSP responder were not taken from the Thawte
+// responder, since CreateResponse requires that we have the private key.
+// Instead, they were generated randomly.
+const responderPrivateKeyHex = "308204a40201000282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef" +
+	"1099f0f6616ec5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df" +
+	"1701dc6ccfbcbec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074f" +
+	"fde8a99d5b723350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14" +
+	"c9fc0f27b8989ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa7" +
+	"7e7332971c7d285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f" +
+	"1290bafd97e655b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb9" +
+	"6222b12ace31a77dcf920334dc94581b02030100010282010100bcf0b93d7238bda329a8" +
+	"72e7149f61bcb37c154330ccb3f42a85c9002c2e2bdea039d77d8581cd19bed94078794e" +
+	"56293d601547fc4bf6a2f9002fe5772b92b21b254403b403585e3130cc99ccf08f0ef81a" +
+	"575b38f597ba4660448b54f44bfbb97072b5a2bf043bfeca828cf7741d13698e3f38162b" +
+	"679faa646b82abd9a72c5c7d722c5fc577a76d2c2daac588accad18516d1bbad10b0dfa2" +
+	"05cfe246b59e28608a43942e1b71b0c80498075121de5b900d727c31c42c78cf1db5c0aa" +
+	"5b491e10ea4ed5c0962aaf2ae025dd81fa4ce490d9d6b4a4465411d8e542fc88617e5695" +
+	"1aa4fc8ea166f2b4d0eb89ef17f2b206bd5f1014bf8fe0e71fe62f2cccf102818100f2dc" +
+	"ddf878d553286daad68bac4070a82ffec3dc4666a2750f47879eec913f91836f1d976b60" +
+	"daf9356e078446dafab5bd2e489e5d64f8572ba24a4ba4f3729b5e106c4dd831cc2497a7" +
+	"e6c7507df05cb64aeb1bbc81c1e340d58b5964cf39cff84ea30c29ec5d3f005ee1362698" +
+	"07395037955955655292c3e85f6187fa1f9502818100f4a33c102630840705f8c778a47b" +
+	"87e8da31e68809af981ac5e5999cf1551685d761cdf0d6520361b99aebd5777a940fa64d" +
+	"327c09fa63746fbb3247ec73a86edf115f1fe5c83598db803881ade71c33c6e956118345" +
+	"497b98b5e07bb5be75971465ec78f2f9467e1b74956ca9d4c7c3e314e742a72d8b33889c" +
+	"6c093a466cef0281801d3df0d02124766dd0be98349b19eb36a508c4e679e793ba0a8bef" +
+	"4d786888c1e9947078b1ea28938716677b4ad8c5052af12eb73ac194915264a913709a0b" +
+	"7b9f98d4a18edd781a13d49899f91c20dbd8eb2e61d991ba19b5cdc08893f5cb9d39e5a6" +
+	"0629ea16d426244673b1b3ee72bd30e41fac8395acac40077403de5efd028180050731dd" +
+	"d71b1a2b96c8d538ba90bb6b62c8b1c74c03aae9a9f59d21a7a82b0d572ef06fa9c807bf" +
+	"c373d6b30d809c7871df96510c577421d9860c7383fda0919ece19996b3ca13562159193" +
+	"c0c246471e287f975e8e57034e5136aaf44254e2650def3d51292474c515b1588969112e" +
+	"0a85cc77073e9d64d2c2fc497844284b02818100d71d63eabf416cf677401ebf965f8314" +
+	"120b568a57dd3bd9116c629c40dc0c6948bab3a13cc544c31c7da40e76132ef5dd3f7534" +
+	"45a635930c74326ae3df0edd1bfb1523e3aa259873ac7cf1ac31151ec8f37b528c275622" +
+	"48f99b8bed59fd4da2576aa6ee20d93a684900bf907e80c66d6e2261ae15e55284b4ed9d" +
+	"6bdaa059"
+
+const responderCertHex = "308202e2308201caa003020102020101300d06092a864886f70d01010b05003019311730" +
+	"150603550403130e4f43535020526573706f6e646572301e170d31353031333031353530" +
+	"33335a170d3136303133303135353033335a3019311730150603550403130e4f43535020" +
+	"526573706f6e64657230820122300d06092a864886f70d01010105000382010f00308201" +
+	"0a0282010100e8155f2d3e6f2e8d14c62a788bd462f9f844e7a6977c83ef1099f0f6616e" +
+	"c5265b56f356e62c5400f0b06a2e7945a82752c636df32a895152d6074df1701dc6ccfbc" +
+	"bec75a70bd2b55ae2be7e6cad3b5fd4cd5b7790ab401a436d3f5f346074ffde8a99d5b72" +
+	"3350f0a112076614b12ef79c78991b119453445acf2416ab0046b540db14c9fc0f27b898" +
+	"9ad0f63aa4b8aefc91aa8a72160c36307c60fec78a93d3fddf4259902aa77e7332971c7d" +
+	"285b6a04f648993c6922a3e9da9adf5f81508c3228791843e5d49f24db2f1290bafd97e6" +
+	"55b1049a199f652cd603c4fafa330c390b0da78fbbc67e8fa021cbd74eb96222b12ace31" +
+	"a77dcf920334dc94581b0203010001a3353033300e0603551d0f0101ff04040302078030" +
+	"130603551d25040c300a06082b06010505070309300c0603551d130101ff04023000300d" +
+	"06092a864886f70d01010b05000382010100718012761b5063e18f0dc44644d8e6ab8612" +
+	"31c15fd5357805425d82aec1de85bf6d3e30fce205e3e3b8b795bbe52e40a439286d2288" +
+	"9064f4aeeb150359b9425f1da51b3a5c939018555d13ac42c565a0603786a919328f3267" +
+	"09dce52c22ad958ecb7873b9771d1148b1c4be2efe80ba868919fc9f68b6090c2f33c156" +
+	"d67156e42766a50b5d51e79637b7e58af74c2a951b1e642fa7741fec982cc937de37eff5" +
+	"9e2005d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf9" +
+	"66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
+	"3a25439a94299a65a709756c7a3e568be049d5c38839"