Jan Dittberner
63c3716b5b
small refactoring to unify package structure. Use crypto.rand for serial number generation in tests.
136 lines
3.5 KiB
Go
136 lines
3.5 KiB
Go
package revoking
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"fmt"
|
|
"log"
|
|
"math/big"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var OidCRLReason = asn1.ObjectIdentifier{2, 5, 29, 21}
|
|
|
|
type CRLReason int
|
|
|
|
// CRL reason codes as defined in RFC 5280 section 5.3.1
|
|
const (
|
|
CRLReasonUnspecified CRLReason = 0
|
|
CRLReasonKeyCompromise CRLReason = 1
|
|
CRLReasonCACompromise CRLReason = 2
|
|
CRLReasonAffiliationChanged CRLReason = 3
|
|
CRLReasonSuperseded CRLReason = 4
|
|
CRLReasonCessationOfOperation CRLReason = 5
|
|
CRLReasonCertificateHold CRLReason = 6
|
|
CRLReasonRemoveFromCRL CRLReason = 8
|
|
CRLReasonPrivilegeWithdrawn CRLReason = 9
|
|
CRLReasonAACompromise CRLReason = 10
|
|
)
|
|
|
|
var crlReasonNames = map[CRLReason]string{
|
|
CRLReasonUnspecified: "unspecified",
|
|
CRLReasonKeyCompromise: "keyCompromise",
|
|
CRLReasonCACompromise: "CACompromise",
|
|
CRLReasonAffiliationChanged: "affiliationChanged",
|
|
CRLReasonSuperseded: "superseded",
|
|
CRLReasonCessationOfOperation: "cessationOfOperation",
|
|
CRLReasonCertificateHold: "certificateHold",
|
|
CRLReasonRemoveFromCRL: "removeFromCRL",
|
|
CRLReasonPrivilegeWithdrawn: "privilegeWithdrawn",
|
|
CRLReasonAACompromise: "AACompromise",
|
|
}
|
|
|
|
func (r CRLReason) String() string {
|
|
if reason, ok := crlReasonNames[r]; ok {
|
|
return reason
|
|
}
|
|
|
|
return crlReasonNames[CRLReasonUnspecified]
|
|
}
|
|
|
|
func (r CRLReason) BuildExtension() pkix.Extension {
|
|
extBytes, err := asn1.Marshal(r)
|
|
if err != nil {
|
|
// we can panic here because all values of CRLReason must be ASN.1 marshal-able
|
|
log.Panicf("could not marshal revocation reason: %v", err)
|
|
}
|
|
|
|
return pkix.Extension{Id: OidCRLReason, Value: extBytes}
|
|
}
|
|
|
|
// ParseReason takes a reason string and performs a case-insensitive match to a reason code
|
|
func ParseReason(rs string) CRLReason {
|
|
for key, name := range crlReasonNames {
|
|
if strings.EqualFold(name, rs) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
return CRLReasonUnspecified
|
|
}
|
|
|
|
type X509Revoking struct {
|
|
repository Repository
|
|
crlAlgorithm x509.SignatureAlgorithm
|
|
crlIssuer *x509.Certificate
|
|
signer crypto.Signer
|
|
}
|
|
|
|
type RevokeCertificate struct {
|
|
serialNumber *big.Int
|
|
reason CRLReason
|
|
}
|
|
|
|
type CertificateRevoked struct {
|
|
serialNumber *big.Int
|
|
revocationTime time.Time
|
|
reason string
|
|
}
|
|
|
|
type CRLInformation struct {
|
|
CRL []byte // DER encoded CRL
|
|
}
|
|
|
|
func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*pkix.RevokedCertificate, error) {
|
|
revoked := &pkix.RevokedCertificate{
|
|
SerialNumber: revokeCertificate.serialNumber,
|
|
RevocationTime: time.Now(),
|
|
Extensions: []pkix.Extension{revokeCertificate.reason.BuildExtension()},
|
|
}
|
|
|
|
if err := r.repository.StoreRevocation(revoked); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return revoked, nil
|
|
}
|
|
|
|
func (r *X509Revoking) CreateCRL() (*CRLInformation, error) {
|
|
revoked, err := r.repository.RevokedCertificates()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get revocation information: %w", err)
|
|
}
|
|
|
|
list, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
SignatureAlgorithm: r.crlAlgorithm,
|
|
RevokedCertificates: revoked,
|
|
}, r.crlIssuer, r.signer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not sign revocation list: %w", err)
|
|
}
|
|
|
|
return &CRLInformation{CRL: list}, nil
|
|
}
|
|
|
|
func NewX509Revoking(
|
|
repo Repository,
|
|
crlAlgorithm x509.SignatureAlgorithm,
|
|
issuer *x509.Certificate,
|
|
signer crypto.Signer,
|
|
) *X509Revoking {
|
|
return &X509Revoking{repository: repo, crlAlgorithm: crlAlgorithm, crlIssuer: issuer, signer: signer}
|
|
}
|