x/crypto/ocsp: return errors to reflect OCSP errors.

Previously, OCSP errors (like “malformed request”, not “that certificate
is revoked”) were intended to result in a Response being returned with
Status set to ServerFailed. However, since an “optional” tag was missing
in the ASN.1, a parse error was actually returned.

This CL changes that behaviour so that ParseResponse will now return an
error for these responses. That error will be a ResponseError, allowing
callers to find the exact error code.

Change-Id: I4f8ae5ba39203c2c204fb1d65471d1427bf68b25
Reviewed-on: https://go-review.googlesource.com/18944
Reviewed-by: Adam Langley <agl@golang.org>
diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go
index 61847a5..ea61cf4 100644
--- a/ocsp/ocsp.go
+++ b/ocsp/ocsp.go
@@ -19,23 +19,60 @@
 	"encoding/asn1"
 	"errors"
 	"math/big"
+	"strconv"
 	"time"
 )
 
 var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
 
-// These are internal structures that reflect the ASN.1 structure of an OCSP
-// response. See RFC 2560, section 4.2.
+// ResponseStatus contains the result of an OCSP request. See
+// https://tools.ietf.org/html/rfc6960#section-2.3
+type ResponseStatus int
 
 const (
-	ocspSuccess       = 0
-	ocspMalformed     = 1
-	ocspInternalError = 2
-	ocspTryLater      = 3
-	ocspSigRequired   = 4
-	ocspUnauthorized  = 5
+	Success           ResponseStatus = 0
+	Malformed         ResponseStatus = 1
+	InternalError     ResponseStatus = 2
+	TryLater          ResponseStatus = 3
+	// Status code four is ununsed in OCSP. See
+	// https://tools.ietf.org/html/rfc6960#section-4.2.1
+	SignatureRequired ResponseStatus = 5
+	Unauthorized      ResponseStatus = 6
 )
 
+func (r ResponseStatus) String() string {
+	switch r {
+	case Success:
+		return "success"
+	case Malformed:
+		return "malformed"
+	case InternalError:
+		return "internal error"
+	case TryLater:
+		return "try later"
+	case SignatureRequired:
+		return "signature required"
+	case Unauthorized:
+		return "unauthorized"
+	default:
+		return "unknown OCSP status: " + strconv.Itoa(int(r))
+	}
+}
+
+// ResponseError is an error that may be returned by ParseResponse to indicate
+// that the response itself is an error, not just that its indicating that a
+// certificate is revoked, unknown, etc.
+type ResponseError struct {
+	Status ResponseStatus
+}
+
+func (r ResponseError) Error() string {
+	return "ocsp: error from server: " + r.Status.String()
+}
+
+// These are internal structures that reflect the ASN.1 structure of an OCSP
+// response. See RFC 2560, section 4.2.
+
 type certID struct {
 	HashAlgorithm pkix.AlgorithmIdentifier
 	NameHash      []byte
@@ -60,7 +97,7 @@
 
 type responseASN1 struct {
 	Status   asn1.Enumerated
-	Response responseBytes `asn1:"explicit,tag:0"`
+	Response responseBytes `asn1:"explicit,tag:0,optional"`
 }
 
 type responseBytes struct {
@@ -236,11 +273,13 @@
 	// Good means that the certificate is valid.
 	Good = iota
 	// Revoked means that the certificate has been deliberately revoked.
-	Revoked = iota
+	Revoked
 	// Unknown means that the OCSP responder doesn't know about the certificate.
-	Unknown = iota
-	// ServerFailed means that the OCSP responder failed to process the request.
-	ServerFailed = iota
+	Unknown
+	// ServerFailed is unused and was never used (see
+	// https://go-review.googlesource.com/#/c/18944). ParseResponse will
+	// return a ResponseError when an error response is parsed.
+	ServerFailed
 )
 
 // The enumerated reasons for revoking a certificate.  See RFC 5280.
@@ -269,7 +308,7 @@
 // Response represents an OCSP response containing a single SingleResponse. See
 // RFC 6960.
 type Response struct {
-	// Status is one of {Good, Revoked, Unknown, ServerFailed}
+	// Status is one of {Good, Revoked, Unknown}
 	Status                                        int
 	SerialNumber                                  *big.Int
 	ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
@@ -358,8 +397,10 @@
 // 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
-// it will be used to validate the signature or embedded certificate. Invalid
-// signatures or parse failures will result in a ParseError.
+// it will be used to validate the signature or embedded certificate.
+//
+// Invalid signatures or parse failures will result in a ParseError. Error
+// responses will result in a ResponseError.
 func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) {
 	var resp responseASN1
 	rest, err := asn1.Unmarshal(bytes, &resp)
@@ -370,10 +411,8 @@
 		return nil, ParseError("trailing data in OCSP response")
 	}
 
-	ret := new(Response)
-	if resp.Status != ocspSuccess {
-		ret.Status = ServerFailed
-		return ret, nil
+	if status := ResponseStatus(resp.Status); status != Success {
+		return nil, ResponseError{status}
 	}
 
 	if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
@@ -394,9 +433,11 @@
 		return nil, ParseError("OCSP response contains bad number of responses")
 	}
 
-	ret.TBSResponseData = basicResp.TBSResponseData.Raw
-	ret.Signature = basicResp.Signature.RightAlign()
-	ret.SignatureAlgorithm = getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm)
+	ret := &Response{
+		TBSResponseData:    basicResp.TBSResponseData.Raw,
+		Signature:          basicResp.Signature.RightAlign(),
+		SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm),
+	}
 
 	if len(basicResp.Certificates) > 0 {
 		ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
@@ -623,7 +664,7 @@
 	}
 
 	return asn1.Marshal(responseASN1{
-		Status: ocspSuccess,
+		Status: asn1.Enumerated(Success),
 		Response: responseBytes{
 			ResponseType: idPKIXOCSPBasic,
 			Response:     responseDER,
diff --git a/ocsp/ocsp_test.go b/ocsp/ocsp_test.go
index 5b15c9b..3386849 100644
--- a/ocsp/ocsp_test.go
+++ b/ocsp/ocsp_test.go
@@ -252,6 +252,19 @@
 	}
 }
 
+func TestErrorResponse(t *testing.T) {
+	responseBytes, _ := hex.DecodeString(errorResponseHex)
+	_, err := ParseResponse(responseBytes, nil)
+
+	respErr, ok := err.(ResponseError)
+	if !ok {
+		t.Fatalf("expected ResponseError from ParseResponse but got %#v", err)
+	}
+	if respErr.Status != Malformed {
+		t.Fatalf("expected Malformed status from ParseResponse but got %d", respErr.Status)
+	}
+}
+
 // This OCSP response was taken from Thawte's public OCSP responder.
 // To recreate:
 //   $ openssl s_client -tls1 -showcerts -servername www.google.com -connect www.google.com:443
@@ -567,3 +580,5 @@
 	"9e2005d5939bfc031589ca143e6e8ab83f40ee08cc20a6b4a95a318352c28d18528dcaf9" +
 	"66705de17afa19d6e8ae91ddf33179d16ebb6ac2c69cae8373d408ebf8c55308be6c04d9" +
 	"3a25439a94299a65a709756c7a3e568be049d5c38839"
+
+const errorResponseHex = "30030a0101"