ocsp: use asn1.Enumerated for revocation reason

In the initial patch enabling generation of OCSP responses, the Reason
field in the revokedInfo struct used for serializing responses was set
to type int. That type maps to the ASN.1 type INTEGER, not ENUMERATED,
as required by RFC 6960. As a result, if you serialize an OCSP
resonse with the Reason field populated, then it will be rejected as
malformed by compliant OCSP parsers.

This patch changes the type of the Reason field in revokedInfo to
asn1.Enumerated. It leaves the RevocationReason field in the public
Response struct as int, and converts between the two.  The patch
also adds constant for the defined revocation reasons.

Change-Id: I97205319503f447cde12d9a0bb0bd1a8db7a66ee
Reviewed-on: https://go-review.googlesource.com/13964
Reviewed-by: Adam Langley <agl@golang.org>
diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go
index 1850e1b..99120b5 100644
--- a/ocsp/ocsp.go
+++ b/ocsp/ocsp.go
@@ -94,8 +94,8 @@
 }
 
 type revokedInfo struct {
-	RevocationTime time.Time `asn1:"generalized"`
-	Reason         int       `asn1:"explicit,tag:0,optional"`
+	RevocationTime time.Time       `asn1:"generalized"`
+	Reason         asn1.Enumerated `asn1:"explicit,tag:0,optional"`
 }
 
 var (
@@ -230,6 +230,7 @@
 
 // This is the exposed reflection of the internal OCSP structures.
 
+// The status values that can be expressed in OCSP.  See RFC 6960.
 const (
 	// Good means that the certificate is valid.
 	Good = iota
@@ -241,6 +242,21 @@
 	ServerFailed = iota
 )
 
+// The enumerated reasons for revoking a certificate.  See RFC 5280.
+const (
+	Unspecified          = iota
+	KeyCompromise        = iota
+	CACompromise         = iota
+	AffiliationChanged   = iota
+	Superseded           = iota
+	CessationOfOperation = iota
+	CertificateHold      = iota
+	_                    = iota
+	RemoveFromCRL        = iota
+	PrivilegeWithdrawn   = iota
+	AACompromise         = iota
+)
+
 // Request represents an OCSP request. See RFC 2560.
 type Request struct {
 	HashAlgorithm  crypto.Hash
@@ -399,7 +415,7 @@
 	default:
 		ret.Status = Revoked
 		ret.RevokedAt = r.Revoked.RevocationTime
-		ret.RevocationReason = r.Revoked.Reason
+		ret.RevocationReason = int(r.Revoked.Reason)
 	}
 
 	ret.ProducedAt = basicResp.TBSResponseData.ProducedAt
@@ -530,7 +546,7 @@
 	case Revoked:
 		innerResponse.Revoked = revokedInfo{
 			RevocationTime: template.RevokedAt.UTC(),
-			Reason:         template.RevocationReason,
+			Reason:         asn1.Enumerated(template.RevocationReason),
 		}
 	}
 
diff --git a/ocsp/ocsp_test.go b/ocsp/ocsp_test.go
index 5ea9eb2..d55682f 100644
--- a/ocsp/ocsp_test.go
+++ b/ocsp/ocsp_test.go
@@ -26,9 +26,9 @@
 	}
 
 	expected := Response{
-		Status:           0,
+		Status:           Good,
 		SerialNumber:     big.NewInt(0x1d0fa),
-		RevocationReason: 0,
+		RevocationReason: Unspecified,
 		ThisUpdate:       time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC),
 		NextUpdate:       time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC),
 	}
@@ -171,7 +171,7 @@
 		ThisUpdate:       thisUpdate,
 		NextUpdate:       nextUpdate,
 		RevokedAt:        thisUpdate,
-		RevocationReason: 1, // keyCompromise
+		RevocationReason: KeyCompromise,
 		Certificate:      responder,
 	}