mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-18 21:21:06 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
676 lines
20 KiB
Go
676 lines
20 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package pki
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/helper/certutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
const (
|
|
crlNumberParam = "crl_number"
|
|
deltaCrlBaseNumberParam = "delta_crl_base_number"
|
|
nextUpdateParam = "next_update"
|
|
crlsParam = "crls"
|
|
formatParam = "format"
|
|
)
|
|
|
|
var (
|
|
akOid = asn1.ObjectIdentifier{2, 5, 29, 35}
|
|
crlNumOid = asn1.ObjectIdentifier{2, 5, 29, 20}
|
|
deltaCrlOid = asn1.ObjectIdentifier{2, 5, 29, 27}
|
|
)
|
|
|
|
func pathResignCrls(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "issuer/" + framework.GenericNameRegex(issuerRefParam) + "/resign-crls",
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixPKIIssuer,
|
|
OperationVerb: "resign",
|
|
OperationSuffix: "crls",
|
|
},
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
issuerRefParam: {
|
|
Type: framework.TypeString,
|
|
Description: `Reference to a existing issuer; either "default"
|
|
for the configured default issuer, an identifier or the name assigned
|
|
to the issuer.`,
|
|
Default: defaultRef,
|
|
},
|
|
crlNumberParam: {
|
|
Type: framework.TypeInt,
|
|
Description: `The sequence number to be written within the CRL Number extension.`,
|
|
},
|
|
deltaCrlBaseNumberParam: {
|
|
Type: framework.TypeInt,
|
|
Description: `Using a zero or greater value specifies the base CRL revision number to encode within
|
|
a Delta CRL indicator extension, otherwise the extension will not be added.`,
|
|
Default: -1,
|
|
},
|
|
nextUpdateParam: {
|
|
Type: framework.TypeString,
|
|
Description: `The amount of time the generated CRL should be
|
|
valid; defaults to 72 hours.`,
|
|
Default: defaultCrlConfig.Expiry,
|
|
},
|
|
crlsParam: {
|
|
Type: framework.TypeStringSlice,
|
|
Description: `A list of PEM encoded CRLs to combine, originally signed by the requested issuer.`,
|
|
},
|
|
formatParam: {
|
|
Type: framework.TypeString,
|
|
Description: `The format of the combined CRL, can be "pem" or "der". If "der", the value will be
|
|
base64 encoded. Defaults to "pem".`,
|
|
Default: "pem",
|
|
},
|
|
},
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
Callback: b.pathUpdateResignCrlsHandler,
|
|
Responses: map[int][]framework.Response{
|
|
http.StatusOK: {{
|
|
Description: "OK",
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"crl": {
|
|
Type: framework.TypeString,
|
|
Description: `CRL`,
|
|
Required: true,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
|
|
HelpSynopsis: `Combine and sign with the provided issuer different CRLs`,
|
|
HelpDescription: `Provide two or more PEM encoded CRLs signed by the issuer,
|
|
normally from separate Vault clusters to be combined and signed.`,
|
|
}
|
|
}
|
|
|
|
func pathSignRevocationList(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "issuer/" + framework.GenericNameRegex(issuerRefParam) + "/sign-revocation-list",
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixPKIIssuer,
|
|
OperationVerb: "sign",
|
|
OperationSuffix: "revocation-list",
|
|
},
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
issuerRefParam: {
|
|
Type: framework.TypeString,
|
|
Description: `Reference to a existing issuer; either "default"
|
|
for the configured default issuer, an identifier or the name assigned
|
|
to the issuer.`,
|
|
Default: defaultRef,
|
|
},
|
|
crlNumberParam: {
|
|
Type: framework.TypeInt,
|
|
Description: `The sequence number to be written within the CRL Number extension.`,
|
|
},
|
|
deltaCrlBaseNumberParam: {
|
|
Type: framework.TypeInt,
|
|
Description: `Using a zero or greater value specifies the base CRL revision number to encode within
|
|
a Delta CRL indicator extension, otherwise the extension will not be added.`,
|
|
Default: -1,
|
|
},
|
|
nextUpdateParam: {
|
|
Type: framework.TypeString,
|
|
Description: `The amount of time the generated CRL should be
|
|
valid; defaults to 72 hours.`,
|
|
Default: defaultCrlConfig.Expiry,
|
|
},
|
|
formatParam: {
|
|
Type: framework.TypeString,
|
|
Description: `The format of the combined CRL, can be "pem" or "der". If "der", the value will be
|
|
base64 encoded. Defaults to "pem".`,
|
|
Default: "pem",
|
|
},
|
|
"revoked_certs": {
|
|
Type: framework.TypeSlice,
|
|
Description: `A list of maps containing the keys serial_number (string), revocation_time (string),
|
|
and extensions (map with keys id (string), critical (bool), value (string))`,
|
|
},
|
|
"extensions": {
|
|
Type: framework.TypeSlice,
|
|
Description: `A list of maps containing extensions with keys id (string), critical (bool),
|
|
value (string)`,
|
|
},
|
|
},
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
Callback: b.pathUpdateSignRevocationListHandler,
|
|
Responses: map[int][]framework.Response{
|
|
http.StatusOK: {{
|
|
Description: "OK",
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"crl": {
|
|
Type: framework.TypeString,
|
|
Description: `CRL`,
|
|
Required: true,
|
|
},
|
|
},
|
|
}},
|
|
},
|
|
},
|
|
},
|
|
|
|
HelpSynopsis: `Generate and sign a CRL based on the provided parameters.`,
|
|
HelpDescription: `Given a list of revoked certificates and other parameters,
|
|
return a signed CRL based on the parameter values.`,
|
|
}
|
|
}
|
|
|
|
func (b *backend) pathUpdateResignCrlsHandler(ctx context.Context, request *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
if b.useLegacyBundleCaStorage() {
|
|
return logical.ErrorResponse("This API cannot be used until the migration has completed"), nil
|
|
}
|
|
|
|
issuerRef := getIssuerRef(data)
|
|
crlNumber := data.Get(crlNumberParam).(int)
|
|
deltaCrlBaseNumber := data.Get(deltaCrlBaseNumberParam).(int)
|
|
nextUpdateStr := data.Get(nextUpdateParam).(string)
|
|
rawCrls := data.Get(crlsParam).([]string)
|
|
|
|
format, err := parseCrlFormat(data.Get(formatParam).(string))
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
nextUpdateOffset, err := parseutil.ParseDurationSecond(nextUpdateStr)
|
|
if err != nil {
|
|
return logical.ErrorResponse("invalid value for %s: %v", nextUpdateParam, err), nil
|
|
}
|
|
|
|
if nextUpdateOffset <= 0 {
|
|
return logical.ErrorResponse("%s parameter must be greater than 0", nextUpdateParam), nil
|
|
}
|
|
|
|
if crlNumber < 0 {
|
|
return logical.ErrorResponse("%s parameter must be 0 or greater", crlNumberParam), nil
|
|
}
|
|
if deltaCrlBaseNumber < -1 {
|
|
return logical.ErrorResponse("%s parameter must be -1 or greater", deltaCrlBaseNumberParam), nil
|
|
}
|
|
|
|
if issuerRef == "" {
|
|
return logical.ErrorResponse("%s parameter cannot be blank", issuerRefParam), nil
|
|
}
|
|
|
|
providedCrls, err := decodePemCrls(rawCrls)
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
sc := b.makeStorageContext(ctx, request.Storage)
|
|
caBundle, err := getCaBundle(sc, issuerRef)
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
if err := verifyCrlsAreFromIssuersKey(caBundle.Certificate, providedCrls); err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
revokedCerts, warnings, err := getAllRevokedCertsFromPem(providedCrls)
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
now := time.Now()
|
|
template := &x509.RevocationList{
|
|
SignatureAlgorithm: caBundle.RevocationSigAlg,
|
|
RevokedCertificates: revokedCerts,
|
|
Number: big.NewInt(int64(crlNumber)),
|
|
ThisUpdate: now,
|
|
NextUpdate: now.Add(nextUpdateOffset),
|
|
}
|
|
|
|
if deltaCrlBaseNumber > -1 {
|
|
ext, err := certutil.CreateDeltaCRLIndicatorExt(int64(deltaCrlBaseNumber))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not create crl delta indicator extension: %v", err)
|
|
}
|
|
template.ExtraExtensions = []pkix.Extension{ext}
|
|
}
|
|
|
|
crlBytes, err := x509.CreateRevocationList(rand.Reader, template, caBundle.Certificate, caBundle.PrivateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating new CRL: %w", err)
|
|
}
|
|
|
|
body := encodeResponse(crlBytes, format == "der")
|
|
|
|
return &logical.Response{
|
|
Warnings: warnings,
|
|
Data: map[string]interface{}{
|
|
"crl": body,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (b *backend) pathUpdateSignRevocationListHandler(ctx context.Context, request *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
if b.useLegacyBundleCaStorage() {
|
|
return logical.ErrorResponse("This API cannot be used until the migration has completed"), nil
|
|
}
|
|
|
|
issuerRef := getIssuerRef(data)
|
|
crlNumber := data.Get(crlNumberParam).(int)
|
|
deltaCrlBaseNumber := data.Get(deltaCrlBaseNumberParam).(int)
|
|
nextUpdateStr := data.Get(nextUpdateParam).(string)
|
|
nextUpdateOffset, err := parseutil.ParseDurationSecond(nextUpdateStr)
|
|
if err != nil {
|
|
return logical.ErrorResponse("invalid value for %s: %v", nextUpdateParam, err), nil
|
|
}
|
|
|
|
if nextUpdateOffset <= 0 {
|
|
return logical.ErrorResponse("%s parameter must be greater than 0", nextUpdateParam), nil
|
|
}
|
|
|
|
if crlNumber < 0 {
|
|
return logical.ErrorResponse("%s parameter must be 0 or greater", crlNumberParam), nil
|
|
}
|
|
if deltaCrlBaseNumber < -1 {
|
|
return logical.ErrorResponse("%s parameter must be -1 or greater", deltaCrlBaseNumberParam), nil
|
|
}
|
|
|
|
if issuerRef == "" {
|
|
return logical.ErrorResponse("%s parameter cannot be blank", issuerRefParam), nil
|
|
}
|
|
|
|
format, err := parseCrlFormat(data.Get(formatParam).(string))
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
revokedCerts, err := parseRevokedCertsParam(data.Get("revoked_certs").([]interface{}))
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
crlExtensions, err := parseExtensionsParam(data.Get("extensions").([]interface{}))
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
sc := b.makeStorageContext(ctx, request.Storage)
|
|
caBundle, err := getCaBundle(sc, issuerRef)
|
|
if err != nil {
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
}
|
|
|
|
if deltaCrlBaseNumber > -1 {
|
|
ext, err := certutil.CreateDeltaCRLIndicatorExt(int64(deltaCrlBaseNumber))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not create crl delta indicator extension: %v", err)
|
|
}
|
|
crlExtensions = append(crlExtensions, ext)
|
|
}
|
|
|
|
now := time.Now()
|
|
template := &x509.RevocationList{
|
|
SignatureAlgorithm: caBundle.RevocationSigAlg,
|
|
RevokedCertificates: revokedCerts,
|
|
Number: big.NewInt(int64(crlNumber)),
|
|
ThisUpdate: now,
|
|
NextUpdate: now.Add(nextUpdateOffset),
|
|
ExtraExtensions: crlExtensions,
|
|
}
|
|
|
|
crlBytes, err := x509.CreateRevocationList(rand.Reader, template, caBundle.Certificate, caBundle.PrivateKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating new CRL: %w", err)
|
|
}
|
|
|
|
body := encodeResponse(crlBytes, format == "der")
|
|
|
|
return &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"crl": body,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func parseRevokedCertsParam(revokedCerts []interface{}) ([]pkix.RevokedCertificate, error) {
|
|
var parsedCerts []pkix.RevokedCertificate
|
|
seenSerials := make(map[*big.Int]int)
|
|
for i, entry := range revokedCerts {
|
|
if revokedCert, ok := entry.(map[string]interface{}); ok {
|
|
serialNum, err := parseSerialNum(revokedCert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed parsing serial_number from entry %d: %w", i, err)
|
|
}
|
|
|
|
if origEntry, exists := seenSerials[serialNum]; exists {
|
|
serialNumStr := revokedCert["serial_number"]
|
|
return nil, fmt.Errorf("duplicate serial number: %s, original entry %d and %d", serialNumStr, origEntry, i)
|
|
}
|
|
|
|
seenSerials[serialNum] = i
|
|
|
|
revocationTime, err := parseRevocationTime(revokedCert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed parsing revocation_time from entry %d: %w", i, err)
|
|
}
|
|
|
|
extensions, err := parseCertExtensions(revokedCert)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed parsing extensions from entry %d: %w", i, err)
|
|
}
|
|
|
|
parsedCerts = append(parsedCerts, pkix.RevokedCertificate{
|
|
SerialNumber: serialNum,
|
|
RevocationTime: revocationTime,
|
|
Extensions: extensions,
|
|
})
|
|
}
|
|
}
|
|
|
|
return parsedCerts, nil
|
|
}
|
|
|
|
func parseCertExtensions(cert map[string]interface{}) ([]pkix.Extension, error) {
|
|
extRaw, exists := cert["extensions"]
|
|
if !exists || extRaw == nil || extRaw == "" {
|
|
// We don't require extensions to be populated
|
|
return []pkix.Extension{}, nil
|
|
}
|
|
|
|
extListRaw, ok := extRaw.([]interface{})
|
|
if !ok {
|
|
return nil, errors.New("'extensions' field did not contain a slice")
|
|
}
|
|
|
|
return parseExtensionsParam(extListRaw)
|
|
}
|
|
|
|
func parseExtensionsParam(extRawList []interface{}) ([]pkix.Extension, error) {
|
|
var extensions []pkix.Extension
|
|
seenOid := make(map[string]struct{})
|
|
for i, entryRaw := range extRawList {
|
|
entry, ok := entryRaw.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("extension entry %d not a map", i)
|
|
}
|
|
extension, err := parseExtension(entry)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed parsing extension entry %d: %w", i, err)
|
|
}
|
|
|
|
parsedIdStr := extension.Id.String()
|
|
if _, exists := seenOid[parsedIdStr]; exists {
|
|
return nil, fmt.Errorf("duplicate extension id: %s", parsedIdStr)
|
|
}
|
|
|
|
seenOid[parsedIdStr] = struct{}{}
|
|
|
|
extensions = append(extensions, extension)
|
|
}
|
|
|
|
return extensions, nil
|
|
}
|
|
|
|
func parseExtension(entry map[string]interface{}) (pkix.Extension, error) {
|
|
asnObjectId, err := parseExtAsn1ObjectId(entry)
|
|
if err != nil {
|
|
return pkix.Extension{}, err
|
|
}
|
|
|
|
if asnObjectId.Equal(akOid) {
|
|
return pkix.Extension{}, fmt.Errorf("authority key object identifier (%s) is reserved", akOid.String())
|
|
}
|
|
|
|
if asnObjectId.Equal(crlNumOid) {
|
|
return pkix.Extension{}, fmt.Errorf("crl number object identifier (%s) is reserved", crlNumOid.String())
|
|
}
|
|
|
|
if asnObjectId.Equal(deltaCrlOid) {
|
|
return pkix.Extension{}, fmt.Errorf("delta crl object identifier (%s) is reserved", deltaCrlOid.String())
|
|
}
|
|
|
|
critical, err := parseExtCritical(entry)
|
|
if err != nil {
|
|
return pkix.Extension{}, err
|
|
}
|
|
|
|
extVal, err := parseExtValue(entry)
|
|
if err != nil {
|
|
return pkix.Extension{}, err
|
|
}
|
|
|
|
return pkix.Extension{
|
|
Id: asnObjectId,
|
|
Critical: critical,
|
|
Value: extVal,
|
|
}, nil
|
|
}
|
|
|
|
func parseExtValue(entry map[string]interface{}) ([]byte, error) {
|
|
valRaw, exists := entry["value"]
|
|
if !exists {
|
|
return nil, errors.New("missing 'value' field")
|
|
}
|
|
|
|
valStr, err := parseutil.ParseString(valRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("'value' field value was not a string: %w", err)
|
|
}
|
|
|
|
if len(valStr) == 0 {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
decodeString, err := base64.StdEncoding.DecodeString(valStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed base64 decoding 'value' field: %w", err)
|
|
}
|
|
return decodeString, nil
|
|
}
|
|
|
|
func parseExtCritical(entry map[string]interface{}) (bool, error) {
|
|
critRaw, exists := entry["critical"]
|
|
if !exists || critRaw == nil || critRaw == "" {
|
|
// Optional field, so just return as if they provided the value false.
|
|
return false, nil
|
|
}
|
|
|
|
myBool, err := parseutil.ParseBool(critRaw)
|
|
if err != nil {
|
|
return false, fmt.Errorf("critical field value failed to be parsed: %w", err)
|
|
}
|
|
|
|
return myBool, nil
|
|
}
|
|
|
|
func parseExtAsn1ObjectId(entry map[string]interface{}) (asn1.ObjectIdentifier, error) {
|
|
idRaw, idExists := entry["id"]
|
|
if !idExists {
|
|
return asn1.ObjectIdentifier{}, errors.New("missing id field")
|
|
}
|
|
|
|
oidStr, err := parseutil.ParseString(idRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("'id' field value was not a string: %w", err)
|
|
}
|
|
|
|
if len(oidStr) == 0 {
|
|
return asn1.ObjectIdentifier{}, errors.New("zero length object identifier")
|
|
}
|
|
|
|
// Parse out dot notation
|
|
oidParts := strings.Split(oidStr, ".")
|
|
oid := make(asn1.ObjectIdentifier, len(oidParts), len(oidParts))
|
|
for i := range oidParts {
|
|
oidIntVal, err := strconv.Atoi(oidParts[i])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed parsing asn1 index element %d value %s: %w", i, oidParts[i], err)
|
|
}
|
|
oid[i] = oidIntVal
|
|
}
|
|
return oid, nil
|
|
}
|
|
|
|
func parseRevocationTime(cert map[string]interface{}) (time.Time, error) {
|
|
var revTime time.Time
|
|
revTimeRaw, exists := cert["revocation_time"]
|
|
if !exists {
|
|
return revTime, errors.New("missing 'revocation_time' field")
|
|
}
|
|
revTime, err := parseutil.ParseAbsoluteTime(revTimeRaw)
|
|
if err != nil {
|
|
return revTime, fmt.Errorf("failed parsing time %v: %w", revTimeRaw, err)
|
|
}
|
|
return revTime, nil
|
|
}
|
|
|
|
func parseSerialNum(cert map[string]interface{}) (*big.Int, error) {
|
|
serialNumRaw, serialExists := cert["serial_number"]
|
|
if !serialExists {
|
|
return nil, errors.New("missing 'serial_number' field")
|
|
}
|
|
serialNumStr, err := parseutil.ParseString(serialNumRaw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("'serial_number' field value was not a string: %w", err)
|
|
}
|
|
// Clean up any provided serials to decoder
|
|
for _, separator := range []string{":", ".", "-", " "} {
|
|
serialNumStr = strings.ReplaceAll(serialNumStr, separator, "")
|
|
}
|
|
// Prefer hex.DecodeString over certutil.ParseHexFormatted as we don't need a separator
|
|
serialBytes, err := hex.DecodeString(serialNumStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("'serial_number' failed converting to bytes: %w", err)
|
|
}
|
|
|
|
bigIntSerial := big.Int{}
|
|
bigIntSerial.SetBytes(serialBytes)
|
|
return &bigIntSerial, nil
|
|
}
|
|
|
|
func parseCrlFormat(requestedValue string) (string, error) {
|
|
format := strings.ToLower(requestedValue)
|
|
switch format {
|
|
case "pem", "der":
|
|
return format, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown format value of %s", requestedValue)
|
|
}
|
|
}
|
|
|
|
func verifyCrlsAreFromIssuersKey(caCert *x509.Certificate, crls []*x509.RevocationList) error {
|
|
for i, crl := range crls {
|
|
// At this point we assume if the issuer's key signed the CRL that is a good enough check
|
|
// to validate that we owned/generated the provided CRL.
|
|
if err := crl.CheckSignatureFrom(caCert); err != nil {
|
|
return fmt.Errorf("CRL index: %d was not signed by requested issuer", i)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func encodeResponse(crlBytes []byte, derFormatRequested bool) string {
|
|
if derFormatRequested {
|
|
return base64.StdEncoding.EncodeToString(crlBytes)
|
|
}
|
|
|
|
block := pem.Block{
|
|
Type: "X509 CRL",
|
|
Bytes: crlBytes,
|
|
}
|
|
return string(pem.EncodeToMemory(&block))
|
|
}
|
|
|
|
func getAllRevokedCertsFromPem(crls []*x509.RevocationList) ([]pkix.RevokedCertificate, []string, error) {
|
|
uniqueCert := map[string]pkix.RevokedCertificate{}
|
|
var warnings []string
|
|
for _, crl := range crls {
|
|
for _, curCert := range crl.RevokedCertificates {
|
|
serial := serialFromBigInt(curCert.SerialNumber)
|
|
// Get rid of any extensions the existing certificate might have had.
|
|
curCert.Extensions = []pkix.Extension{}
|
|
|
|
existingCert, exists := uniqueCert[serial]
|
|
if !exists {
|
|
// First time we see the revoked cert
|
|
uniqueCert[serial] = curCert
|
|
continue
|
|
}
|
|
|
|
if existingCert.RevocationTime.Equal(curCert.RevocationTime) {
|
|
// Same revocation times, just skip it
|
|
continue
|
|
}
|
|
|
|
warn := fmt.Sprintf("Duplicate serial %s with different revocation "+
|
|
"times detected, using oldest revocation time", serial)
|
|
warnings = append(warnings, warn)
|
|
|
|
if existingCert.RevocationTime.After(curCert.RevocationTime) {
|
|
uniqueCert[serial] = curCert
|
|
}
|
|
}
|
|
}
|
|
|
|
var revokedCerts []pkix.RevokedCertificate
|
|
for _, cert := range uniqueCert {
|
|
revokedCerts = append(revokedCerts, cert)
|
|
}
|
|
|
|
return revokedCerts, warnings, nil
|
|
}
|
|
|
|
func getCaBundle(sc *storageContext, issuerRef string) (*certutil.CAInfoBundle, error) {
|
|
issuerId, err := sc.resolveIssuerReference(issuerRef)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to resolve issuer %s: %w", issuerRefParam, err)
|
|
}
|
|
|
|
return sc.fetchCAInfoByIssuerId(issuerId, CRLSigningUsage)
|
|
}
|
|
|
|
func decodePemCrls(rawCrls []string) ([]*x509.RevocationList, error) {
|
|
var crls []*x509.RevocationList
|
|
for i, rawCrl := range rawCrls {
|
|
crl, err := decodePemCrl(rawCrl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed decoding crl %d: %w", i, err)
|
|
}
|
|
crls = append(crls, crl)
|
|
}
|
|
|
|
return crls, nil
|
|
}
|
|
|
|
func decodePemCrl(crl string) (*x509.RevocationList, error) {
|
|
block, rest := pem.Decode([]byte(crl))
|
|
if len(rest) != 0 {
|
|
return nil, errors.New("invalid crl; should be one PEM block only")
|
|
}
|
|
|
|
return x509.ParseRevocationList(block.Bytes)
|
|
}
|