2022-04-24 07:25:04 +00:00
|
|
|
/*
|
|
|
|
Copyright 2021-2022 CAcert Inc.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Package revoking takes care of handling certificate revocation requests.
|
2021-08-23 18:53:43 +00:00
|
|
|
package revoking
|
|
|
|
|
|
|
|
import (
|
2022-04-23 17:37:42 +00:00
|
|
|
"crypto"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"encoding/asn1"
|
|
|
|
"fmt"
|
2021-08-23 18:53:43 +00:00
|
|
|
"math/big"
|
2022-04-23 17:37:42 +00:00
|
|
|
"strings"
|
2021-08-23 18:53:43 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
var OidCRLReason = asn1.ObjectIdentifier{2, 5, 29, 21}
|
|
|
|
|
2022-11-20 17:59:37 +00:00
|
|
|
const defaultCRLValidity = 24 * time.Hour
|
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
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 {
|
2022-04-24 10:45:22 +00:00
|
|
|
extBytes, _ := asn1.Marshal(r)
|
2022-04-23 17:37:42 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-08-23 18:53:43 +00:00
|
|
|
type X509Revoking struct {
|
2022-04-23 17:37:42 +00:00
|
|
|
repository Repository
|
|
|
|
crlAlgorithm x509.SignatureAlgorithm
|
|
|
|
crlIssuer *x509.Certificate
|
|
|
|
signer crypto.Signer
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type RevokeCertificate struct {
|
|
|
|
serialNumber *big.Int
|
2022-04-23 17:37:42 +00:00
|
|
|
reason CRLReason
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
|
|
|
|
2022-04-24 10:45:22 +00:00
|
|
|
func NewRevokeCertificate(serialNumber *big.Int, reason CRLReason) *RevokeCertificate {
|
|
|
|
return &RevokeCertificate{
|
|
|
|
serialNumber: serialNumber,
|
|
|
|
reason: reason,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
type CRLInformation struct {
|
|
|
|
CRL []byte // DER encoded CRL
|
|
|
|
}
|
2021-08-23 18:53:43 +00:00
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
func (r *X509Revoking) Revoke(revokeCertificate *RevokeCertificate) (*pkix.RevokedCertificate, error) {
|
|
|
|
revoked := &pkix.RevokedCertificate{
|
|
|
|
SerialNumber: revokeCertificate.serialNumber,
|
|
|
|
RevocationTime: time.Now(),
|
|
|
|
Extensions: []pkix.Extension{revokeCertificate.reason.BuildExtension()},
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := r.repository.StoreRevocation(revoked); err != nil {
|
2022-04-24 07:25:04 +00:00
|
|
|
return nil, fmt.Errorf("could not store revocation %w", err)
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return revoked, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *X509Revoking) CreateCRL() (*CRLInformation, error) {
|
2022-04-23 17:37:42 +00:00
|
|
|
revoked, err := r.repository.RevokedCertificates()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not get revocation information: %w", err)
|
|
|
|
}
|
2021-08-23 18:53:43 +00:00
|
|
|
|
2022-04-24 10:45:22 +00:00
|
|
|
nextNumber, err := r.repository.NextCRLNumber()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not get next CRL number: %w", err)
|
|
|
|
}
|
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
list, err := x509.CreateRevocationList(rand.Reader, &x509.RevocationList{
|
|
|
|
SignatureAlgorithm: r.crlAlgorithm,
|
|
|
|
RevokedCertificates: revoked,
|
2022-04-24 10:45:22 +00:00
|
|
|
Number: nextNumber,
|
2022-11-20 17:59:37 +00:00
|
|
|
NextUpdate: time.Now().UTC().Add(defaultCRLValidity),
|
2022-04-23 17:37:42 +00:00
|
|
|
}, r.crlIssuer, r.signer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not sign revocation list: %w", err)
|
|
|
|
}
|
2021-08-23 18:53:43 +00:00
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
return &CRLInformation{CRL: list}, nil
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 17:37:42 +00:00
|
|
|
func NewX509Revoking(
|
|
|
|
repo Repository,
|
|
|
|
crlAlgorithm x509.SignatureAlgorithm,
|
|
|
|
issuer *x509.Certificate,
|
|
|
|
signer crypto.Signer,
|
|
|
|
) *X509Revoking {
|
|
|
|
return &X509Revoking{repository: repo, crlAlgorithm: crlAlgorithm, crlIssuer: issuer, signer: signer}
|
2021-08-23 18:53:43 +00:00
|
|
|
}
|
2022-11-20 17:59:37 +00:00
|
|
|
|
|
|
|
type Result struct {
|
|
|
|
Data []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type FetchCRLHandler struct {
|
|
|
|
repositories map[string]*X509Revoking
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewFetchCRLHandler(repositories map[string]*X509Revoking) *FetchCRLHandler {
|
|
|
|
return &FetchCRLHandler{repositories: repositories}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *FetchCRLHandler) FetchCRL(issuerID string) (*Result, error) {
|
|
|
|
repo, ok := h.repositories[issuerID]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("unknown issuer ID %s", issuerID)
|
|
|
|
}
|
|
|
|
|
|
|
|
currentCRL, err := repo.CreateCRL()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("could not create CRL for issuer ID %s: %w", issuerID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Result{Data: currentCRL.CRL}, nil
|
|
|
|
}
|