open-vault/builtin/logical/pki/crl_util.go
Jeff Mitchell 29e7ec3e21 A lot of refactoring: move PEM bundle parsing into helper/certutil, so that it is usable by other backends that want to use it to get the necessary data for TLS auth.
Also, enhance the raw cert bundle => parsed cert bundle to make it more useful and perform more validation checks.

More refactoring could be done within the PKI backend itself, but that can wait.

Commit contents (C)2015 Akamai Technologies, Inc. <opensource@akamai.com>
2015-06-17 16:07:20 -04:00

180 lines
4.8 KiB
Go

package pki
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"sync"
"time"
"github.com/hashicorp/vault/logical"
)
type revocationInfo struct {
CertificateBytes []byte `json:"certificate_bytes"`
RevocationTime int64 `json:"revocation_time"`
}
var (
crlLifetime = time.Hour * 72
revokeStorageLock = &sync.Mutex{}
)
func revokeCert(req *logical.Request, serial string) (*logical.Response, error) {
alreadyRevoked := false
var err error
revInfo := revocationInfo{}
certEntry, userErr, intErr := fetchCertBySerial(req, "revoked/", serial)
if certEntry != nil {
// Verify that it is also deleted from certs/
// in case of partial failure from an earlier run.
certEntry, _, _ = fetchCertBySerial(req, "certs/", serial)
if certEntry != nil {
alreadyRevoked = true
revEntry, err := req.Storage.Get("revoked/" + serial)
if 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")
}
} else {
return nil, nil
}
}
if !alreadyRevoked {
certEntry, userErr, intErr = fetchCertBySerial(req, "certs/", serial)
switch {
case userErr != nil:
return logical.ErrorResponse(userErr.Error()), nil
case intErr != nil:
return nil, intErr
}
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
}
revInfo.CertificateBytes = certEntry.Value
revInfo.RevocationTime = time.Now().Unix()
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")
}
}
userErr, intErr = buildCRL(req)
switch {
case userErr != nil:
return logical.ErrorResponse(fmt.Sprintf("Error during CRL building: %s", userErr)), nil
case intErr != nil:
return nil, fmt.Errorf("Error encountered during CRL building: %s", intErr)
}
err = req.Storage.Delete("certs/" + serial)
if err != nil {
return nil, fmt.Errorf("Error deleting cert from valid-certs location")
}
return &logical.Response{
Data: map[string]interface{}{
"revocation_time": revInfo.RevocationTime,
},
}, nil
}
func buildCRL(req *logical.Request) (error, error) {
revokedSerials, err := req.Storage.List("revoked/")
if err != nil {
return nil, fmt.Errorf("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 nil, fmt.Errorf("Unable to fetch revoked cert with serial %s: %s", serial, err)
}
if revokedEntry == nil {
return nil, fmt.Errorf("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 nil, fmt.Errorf("Found revoked serial but actual certificate is empty")
}
err = revokedEntry.DecodeJSON(&revInfo)
if err != nil {
return nil, fmt.Errorf("Error decoding revocation entry for serial %s: %s", serial, err)
}
revokedCert, err := x509.ParseCertificate(revInfo.CertificateBytes)
if err != nil {
return nil, fmt.Errorf("Unable to parse stored revoked certificate with serial %s: %s", serial, err)
}
if revokedCert.NotAfter.Before(time.Now()) {
err = req.Storage.Delete(serial)
if err != nil {
return nil, fmt.Errorf("Unable to delete revoked, expired certificate with serial %s: %s", serial, err)
}
continue
}
revokedCerts = append(revokedCerts, pkix.RevokedCertificate{
SerialNumber: revokedCert.SerialNumber,
RevocationTime: time.Unix(revInfo.RevocationTime, 0),
})
}
signingBundle, caCert, userErr, intErr := fetchCAInfo(req)
switch {
case userErr != nil:
return fmt.Errorf("Could not fetch the CA certificate: %s", userErr), nil
case intErr != nil:
return nil, fmt.Errorf("Error fetching CA certificate: %s", intErr)
}
// TODO: Make expiry configurable
crlBytes, err := caCert.CreateCRL(rand.Reader, signingBundle.PrivateKey, revokedCerts, time.Now(), time.Now().Add(crlLifetime))
if err != nil {
return nil, fmt.Errorf("Error creating new CRL: %s", err)
}
err = req.Storage.Put(&logical.StorageEntry{
Key: "crl",
Value: crlBytes,
})
if err != nil {
return nil, fmt.Errorf("Error storing CRL: %s", err)
}
return nil, nil
}