mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-18 04:27:02 +02:00
This fixes #1911 but not directly; it doesn't address the cause of the panic. However, it turns out that this is the correct fix anyways, because it ensures that the value being logged is RFC3339 format, which is what the time turns into in JSON but not the normal time string value, so what we audit log (and HMAC) matches what we are returning.
210 lines
6.5 KiB
Go
210 lines
6.5 KiB
Go
package pki
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/helper/errutil"
|
|
"github.com/hashicorp/vault/logical"
|
|
)
|
|
|
|
type revocationInfo struct {
|
|
CertificateBytes []byte `json:"certificate_bytes"`
|
|
RevocationTime int64 `json:"revocation_time"`
|
|
RevocationTimeUTC time.Time `json:"revocation_time_utc"`
|
|
}
|
|
|
|
// Revokes a cert, and tries to be smart about error recovery
|
|
func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool) (*logical.Response, error) {
|
|
// As this backend is self-contained and this function does not hook into
|
|
// third parties to manage users or resources, if the mount is tainted,
|
|
// revocation doesn't matter anyways -- the CRL that would be written will
|
|
// be immediately blown away by the view being cleared. So we can simply
|
|
// fast path a successful exit.
|
|
if b.System().Tainted() {
|
|
return nil, nil
|
|
}
|
|
|
|
alreadyRevoked := false
|
|
var revInfo revocationInfo
|
|
|
|
certEntry, err := fetchCertBySerial(req, "revoked/", serial)
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case errutil.UserError:
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
case errutil.InternalError:
|
|
return nil, err
|
|
}
|
|
}
|
|
if certEntry != nil {
|
|
// Set the revocation info to the existing values
|
|
alreadyRevoked = true
|
|
|
|
revEntry, err := req.Storage.Get("revoked/" + serial)
|
|
if revEntry == nil || err != nil {
|
|
return nil, fmt.Errorf("Error getting existing revocation info")
|
|
}
|
|
|
|
err = revEntry.DecodeJSON(&revInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error decoding existing revocation info")
|
|
}
|
|
}
|
|
|
|
if !alreadyRevoked {
|
|
certEntry, err = fetchCertBySerial(req, "certs/", serial)
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case errutil.UserError:
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
case errutil.InternalError:
|
|
return nil, err
|
|
}
|
|
}
|
|
if certEntry == nil {
|
|
return logical.ErrorResponse(fmt.Sprintf("certificate with serial %s not found", serial)), nil
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(certEntry.Value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error parsing certificate")
|
|
}
|
|
if cert == nil {
|
|
return nil, fmt.Errorf("Got a nil certificate")
|
|
}
|
|
|
|
if cert.NotAfter.Before(time.Now()) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Compatibility: Don't revoke CAs if they had leases. New CAs going
|
|
// forward aren't issued leases.
|
|
if cert.IsCA && fromLease {
|
|
return nil, nil
|
|
}
|
|
|
|
currTime := time.Now()
|
|
revInfo.CertificateBytes = certEntry.Value
|
|
revInfo.RevocationTime = currTime.Unix()
|
|
revInfo.RevocationTimeUTC = currTime.UTC()
|
|
|
|
certEntry, err = logical.StorageEntryJSON("revoked/"+serial, revInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error creating revocation entry")
|
|
}
|
|
|
|
err = req.Storage.Put(certEntry)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error saving revoked certificate to new location")
|
|
}
|
|
|
|
}
|
|
|
|
crlErr := buildCRL(b, req)
|
|
switch crlErr.(type) {
|
|
case errutil.UserError:
|
|
return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", crlErr)), nil
|
|
case errutil.InternalError:
|
|
return nil, fmt.Errorf("Error encountered during CRL building: %s", crlErr)
|
|
}
|
|
|
|
resp := &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"revocation_time": revInfo.RevocationTime,
|
|
},
|
|
}
|
|
if !revInfo.RevocationTimeUTC.IsZero() {
|
|
resp.Data["revocation_time_rfc3339"] = revInfo.RevocationTimeUTC.Format(time.RFC3339Nano)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// Builds a CRL by going through the list of revoked certificates and building
|
|
// a new CRL with the stored revocation times and serial numbers.
|
|
func buildCRL(b *backend, req *logical.Request) error {
|
|
revokedSerials, err := req.Storage.List("revoked/")
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error fetching list of revoked certs: %s", err)}
|
|
}
|
|
|
|
revokedCerts := []pkix.RevokedCertificate{}
|
|
var revInfo revocationInfo
|
|
for _, serial := range revokedSerials {
|
|
revokedEntry, err := req.Storage.Get("revoked/" + serial)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Unable to fetch revoked cert with serial %s: %s", serial, err)}
|
|
}
|
|
if revokedEntry == nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Revoked certificate entry for serial %s is nil", serial)}
|
|
}
|
|
if revokedEntry.Value == nil || len(revokedEntry.Value) == 0 {
|
|
// TODO: In this case, remove it and continue? How likely is this to
|
|
// happen? Alternately, could skip it entirely, or could implement a
|
|
// delete function so that there is a way to remove these
|
|
return errutil.InternalError{Err: fmt.Sprintf("Found revoked serial but actual certificate is empty")}
|
|
}
|
|
|
|
err = revokedEntry.DecodeJSON(&revInfo)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error decoding revocation entry for serial %s: %s", serial, err)}
|
|
}
|
|
|
|
revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Unable to parse stored revoked certificate with serial %s: %s", serial, err)}
|
|
}
|
|
|
|
// NOTE: We have to change this to UTC time because the CRL standard
|
|
// mandates it but Go will happily encode the CRL without this.
|
|
newRevCert := pkix.RevokedCertificate{
|
|
SerialNumber: revokedCert.SerialNumber,
|
|
}
|
|
if !revInfo.RevocationTimeUTC.IsZero() {
|
|
newRevCert.RevocationTime = revInfo.RevocationTimeUTC
|
|
} else {
|
|
newRevCert.RevocationTime = time.Unix(revInfo.RevocationTime, 0).UTC()
|
|
}
|
|
revokedCerts = append(revokedCerts, newRevCert)
|
|
}
|
|
|
|
signingBundle, caErr := fetchCAInfo(req)
|
|
switch caErr.(type) {
|
|
case errutil.UserError:
|
|
return errutil.UserError{Err: fmt.Sprintf("Could not fetch the CA certificate: %s", caErr)}
|
|
case errutil.InternalError:
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error fetching CA certificate: %s", caErr)}
|
|
}
|
|
|
|
crlLifetime := b.crlLifetime
|
|
crlInfo, err := b.CRL(req.Storage)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error fetching CRL config information: %s", err)}
|
|
}
|
|
if crlInfo != nil {
|
|
crlDur, err := time.ParseDuration(crlInfo.Expiry)
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error parsing CRL duration of %s", crlInfo.Expiry)}
|
|
}
|
|
crlLifetime = crlDur
|
|
}
|
|
|
|
crlBytes, err := signingBundle.Certificate.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime))
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error creating new CRL: %s", err)}
|
|
}
|
|
|
|
err = req.Storage.Put(&logical.StorageEntry{
|
|
Key: "crl",
|
|
Value: crlBytes,
|
|
})
|
|
if err != nil {
|
|
return errutil.InternalError{Err: fmt.Sprintf("Error storing CRL: %s", err)}
|
|
}
|
|
|
|
return nil
|
|
}
|