crlVerification/utils/reason.go (105 lines of code) (raw):
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package utils
import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"reflect"
)
type RevocationReason int
// The following is an enumeration that, for a given revocation, the CA is claiming is the reason for said revocation.
//
// https://tools.ietf.org/html/rfc5280#section-5.3.1
//
// Integers 0-10 (inclusive) are reserved by RFC 5280. As this field is optional, this program reserves -1
// to mean "not given" (either from the CCADB or a CA).
//
// (0) unspecified
// (1) keyCompromise
// (2) cACompromise
// (3) affiliationChanged
// (4) superseded
// (5) cessationOfOperation
// (6) certificateHold
// (8) removeFromCRL
// (9) privilegeWithdrawn
// (10) aACompromise
const NOT_GIVEN RevocationReason = -1
const (
UNSPECIFIED RevocationReason = iota
KEY_COMPROMISE
CA_COMPROMISE
AFFILIATION_CHANGE
SUPERSEDED
CESSATION_OF_OPERATION
CERTIFICATE_HOLD
REMOVE_FROM_CRL
PRIVILEGE_WITHDRAWN
AA_COMPROMISE
)
func (r RevocationReason) String() string {
switch r {
case NOT_GIVEN:
return "no reason given"
case UNSPECIFIED:
return "(0) unspecified"
case KEY_COMPROMISE:
return "(1) keyCompromise"
case CA_COMPROMISE:
return "(2) cACompromise"
case AFFILIATION_CHANGE:
return "(3) affiliationChanged"
case SUPERSEDED:
return "(4) superseded"
case CESSATION_OF_OPERATION:
return "(5) cessationOfOperation"
case CERTIFICATE_HOLD:
return "(6) certificateHold"
case REMOVE_FROM_CRL:
return "(8) removeFromCRL"
case PRIVILEGE_WITHDRAWN:
return "(9) privilegeWithdrawn"
case AA_COMPROMISE:
return "(10) aACompromise"
default:
panic(fmt.Sprintf(`programming error: accounted for RevocationReason enum %v`, int(r)))
}
}
func FromString(str *string) (RevocationReason, error) {
if str == nil {
return NOT_GIVEN, nil
}
switch *str {
case "(0) unspecified":
return UNSPECIFIED, nil
case "(1) keyCompromise":
return KEY_COMPROMISE, nil
case "(2) cACompromise":
return CA_COMPROMISE, nil
case "(3) affiliationChanged":
return AFFILIATION_CHANGE, nil
case "(4) superseded":
return SUPERSEDED, nil
case "(5) cessationOfOperation":
return CESSATION_OF_OPERATION, nil
case "(6) certificateHold":
return CERTIFICATE_HOLD, nil
case "(8) removeFromCRL":
return REMOVE_FROM_CRL, nil
case "(9) privilegeWithdrawn":
return PRIVILEGE_WITHDRAWN, nil
case "(10) aACompromise":
return AA_COMPROMISE, nil
default:
return NOT_GIVEN, errors.New(fmt.Sprintf(`unknown revocation reason "%s""`, *str))
}
}
type RevocationReasonError struct {
wanted RevocationReason
got RevocationReason
}
func (r RevocationReasonError) Error() string {
return fmt.Sprintf("Revocation reasons did not match. We wanted %s, but got %s", r.wanted, r.got)
}
var revocationReasonOID asn1.ObjectIdentifier = []int{2, 5, 29, 21}
func ValidateRevocationReason(cert pkix.RevokedCertificate, ourReason RevocationReason) error {
for _, ext := range cert.Extensions {
// Iterate over the extensions, if any, and check
// to see if the extension is a revocation reason code.
if reflect.DeepEqual(ext.Id, revocationReasonOID) {
// We found it, so lets comapare it.
theirReason := asn1ToRevocationReason(ext.Value)
if theirReason == ourReason {
// Everything is fine - we found it and it matches.
return nil
}
// Otherwise it differed.
return RevocationReasonError{ourReason, theirReason}
}
}
// The CRL did not provide a reason.
if ourReason == NOT_GIVEN {
// So if we didn't ask for one, then everything is ok.
return nil
}
// Else, error.
return RevocationReasonError{ourReason, NOT_GIVEN}
}
func asn1ToRevocationReason(data []byte) RevocationReason {
// An ASN1 Enumerated has a minimum of three bytes.
//
// 0. The number 10, signifying that it is an enum.
// 1. The number of bytes that follow (n).
// 2-n. The enumeration value.
//
// A revocation reason code has only ten values
// so we only need the second index.
return RevocationReason(int(data[2]))
}