mirror of https://github.com/docker/docs.git
Merge pull request #65 from docker/adding-the-not-yummy-kind-of-tofu
Adding the not yummy kind of tofu
This commit is contained in:
commit
a90a8b27b3
File diff suppressed because one or more lines are too long
|
@ -64,7 +64,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
|
||||||
gunOrID := args[0]
|
gunOrID := args[0]
|
||||||
|
|
||||||
// Try to retrieve the ID from the CA store.
|
// Try to retrieve the ID from the CA store.
|
||||||
cert, err := caStore.GetCertificateByKeyID(gunOrID)
|
cert, err := caStore.GetCertificateByCertID(gunOrID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Removing: ")
|
fmt.Printf("Removing: ")
|
||||||
printCert(cert)
|
printCert(cert)
|
||||||
|
@ -78,7 +78,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to retrieve the ID from the Certificate store.
|
// Try to retrieve the ID from the Certificate store.
|
||||||
cert, err = certificateStore.GetCertificateByKeyID(gunOrID)
|
cert, err = certificateStore.GetCertificateByCertID(gunOrID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Removing: ")
|
fmt.Printf("Removing: ")
|
||||||
printCert(cert)
|
printCert(cert)
|
||||||
|
@ -216,12 +216,12 @@ func keysGenerate(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
func printCert(cert *x509.Certificate) {
|
func printCert(cert *x509.Certificate) {
|
||||||
timeDifference := cert.NotAfter.Sub(time.Now())
|
timeDifference := cert.NotAfter.Sub(time.Now())
|
||||||
keyID, err := trustmanager.FingerprintCert(cert)
|
certID, err := trustmanager.FingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("could not fingerprint certificate: %v", err)
|
fatalf("could not fingerprint certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, keyID, math.Floor(timeDifference.Hours()/24))
|
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24))
|
||||||
}
|
}
|
||||||
|
|
||||||
func printKey(keyPath string) {
|
func printKey(keyPath string) {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBpDCCAUqgAwIBAgIRAIquZ7lRJj1Um030Kd7GFXgwCgYIKoZIzj0EAwIwODEa
|
||||||
|
MBgGA1UEChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20v
|
||||||
|
bm90YXJ5MB4XDTE1MDcxNzAwMzE1NFoXDTE3MDcxNjAwMzE1NFowODEaMBgGA1UE
|
||||||
|
ChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20vbm90YXJ5
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjnnozttLzYgIN5fL8ZwYbsMig0pj
|
||||||
|
HSNupVTPjDIrLUYUnoQfG6IQ0E2BMixEGnI/A9WreeXP2oz06LZ4SROMQqM1MDMw
|
||||||
|
DgYDVR0PAQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQC
|
||||||
|
MAAwCgYIKoZIzj0EAwIDSAAwRQIgT9cxottjza9BBQcMsoB/Uf2JYXWgSkp9QMXT
|
||||||
|
8mG4mMICIQDMYWFdgn5u8nDeThJ+bG8Lu5nIGb/NWEOFtU0xQv913Q==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBqDCCAU6gAwIBAgIRAM1vKVhmZuWcrogc3ASBaZUwCgYIKoZIzj0EAwIwOjEb
|
||||||
|
MBkGA1UEChMSc2VjdXJlLmV4YW1wbGUuY29tMRswGQYDVQQDExJzZWN1cmUuZXhh
|
||||||
|
bXBsZS5jb20wHhcNMTUwNzE3MDU1NTIzWhcNMTcwNzE2MDU1NTIzWjA6MRswGQYD
|
||||||
|
VQQKExJzZWN1cmUuZXhhbXBsZS5jb20xGzAZBgNVBAMTEnNlY3VyZS5leGFtcGxl
|
||||||
|
LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI556M7bS82ICDeXy/GcGG7D
|
||||||
|
IoNKYx0jbqVUz4wyKy1GFJ6EHxuiENBNgTIsRBpyPwPVq3nlz9qM9Oi2eEkTjEKj
|
||||||
|
NTAzMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMB
|
||||||
|
Af8EAjAAMAoGCCqGSM49BAMCA0gAMEUCIER2XCkQ8dUWBZEUeT5kABg7neiHPtSL
|
||||||
|
VVE6bJxu2sxlAiEAkRG6u1ieXKGl38gUkCn75Yvo9nOSLdh0gtxUUcOXvUc=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -3,8 +3,6 @@ package keystoremanager
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -36,6 +34,15 @@ const (
|
||||||
rsaRootKeySize = 4096 // Used for new root keys
|
rsaRootKeySize = 4096 // Used for new root keys
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrValidationFail is returned when there is no trusted certificate in any of the
|
||||||
|
// root keys available in the roots.json
|
||||||
|
ErrValidationFail = errors.New("could not validate the path to a trusted root")
|
||||||
|
// ErrRootRotationFail is returned when we fail to do a full root key rotation
|
||||||
|
// by either failing to add the new root certificate, or delete the old ones
|
||||||
|
ErrRootRotationFail = errors.New("could not rotate trust to a new trusted root")
|
||||||
|
)
|
||||||
|
|
||||||
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
|
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error
|
||||||
// if it fails to create the KeyFileStores or load certificates
|
// if it fails to create the KeyFileStores or load certificates
|
||||||
func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) {
|
func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) {
|
||||||
|
@ -166,79 +173,166 @@ attempts to validate the certificate by first checking for an exact match on
|
||||||
the certificate store, and subsequently trying to find a valid chain on the
|
the certificate store, and subsequently trying to find a valid chain on the
|
||||||
trustedCAStore.
|
trustedCAStore.
|
||||||
|
|
||||||
When this is being used with a notary repository, the dnsName parameter should
|
Currently this method operates on a Trust On First Use (TOFU) model: if we
|
||||||
be the GUN associated with the repository.
|
have never seen a certificate for a particular CN, we trust it. If later we see
|
||||||
|
a different certificate for that certificate, we return an ErrValidationFailed error.
|
||||||
|
|
||||||
Example TUF Content for root role:
|
Note that since we only allow trust data to be downloaded over an HTTPS channel
|
||||||
"roles" : {
|
we are using the current web-of-trust to validate the first download of the certificate
|
||||||
"root" : {
|
adding an extra layer of security over the normal (SSH style) trust model.
|
||||||
"threshold" : 1,
|
We shall call this: TOFUS.
|
||||||
"keyids" : [
|
|
||||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
Example TUF Content for root key:
|
ValidateRoot also supports root key rotation, trusting a new certificate that has
|
||||||
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : {
|
been included in the roots.json, and removing trust in the old one.
|
||||||
"keytype" : "RSA",
|
|
||||||
"keyval" : {
|
|
||||||
"private" : "",
|
|
||||||
"public" : "Base64-encoded, PEM encoded x509 Certificate"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
|
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
|
||||||
rootSigned := &data.Root{}
|
logrus.Debugf("entered ValidateRoot with dns: %s", dnsName)
|
||||||
err := json.Unmarshal(root.Signed, rootSigned)
|
rootSigned, err := data.RootFromSigned(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
certs := make(map[string]data.PublicKey)
|
// validKeys will store all the keys that were considered valid either by
|
||||||
for _, keyID := range rootSigned.Roles["root"].KeyIDs {
|
// direct certificate match, or CA chain path
|
||||||
// TODO(dlaw): currently assuming only one cert contained in
|
validKeys := make(map[string]data.PublicKey)
|
||||||
// public key entry. Need to fix when we want to pass in chains.
|
|
||||||
k, _ := pem.Decode([]byte(rootSigned.Keys[keyID].Public()))
|
// allCerts will keep a list of all leafCerts that were found, and is used
|
||||||
decodedCerts, err := x509.ParseCertificates(k.Bytes)
|
// to aid on root certificate rotation
|
||||||
|
allCerts := make(map[string]*x509.Certificate)
|
||||||
|
|
||||||
|
// Before we loop through all root keys available, make sure any exist
|
||||||
|
rootRoles, ok := rootSigned.Signed.Roles["root"]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("no root roles found in tuf metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("found the following root keys in roots.json: %v", rootRoles.KeyIDs)
|
||||||
|
// Iterate over every keyID for the root role inside of roots.json
|
||||||
|
for _, keyID := range rootRoles.KeyIDs {
|
||||||
|
// Decode all the x509 certificates that were bundled with this
|
||||||
|
// Specific root key
|
||||||
|
decodedCerts, err := trustmanager.LoadCertBundleFromPEM([]byte(rootSigned.Signed.Keys[keyID].Public()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
|
|
||||||
// iterate over all decodedCerts and find a non-CA one (should be the last).
|
|
||||||
leafCert := decodedCerts[0]
|
|
||||||
|
|
||||||
|
// Get all non-CA certificates in the decoded certificates
|
||||||
|
leafCerts := trustmanager.GetLeafCerts(decodedCerts)
|
||||||
|
|
||||||
|
// If we got no leaf certificates or we got more than one, fail
|
||||||
|
if len(leafCerts) != 1 {
|
||||||
|
logrus.Debugf("wasn't able to find a leaf certificate in the chain of keyID: %s", keyID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the ID of the leaf certificate
|
||||||
|
leafCert := leafCerts[0]
|
||||||
leafID, err := trustmanager.FingerprintCert(leafCert)
|
leafID, err := trustmanager.FingerprintCert(leafCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that this leaf certificate has a CN that matches the exact gun
|
||||||
|
if leafCert.Subject.CommonName != dnsName {
|
||||||
|
logrus.Debugf("error leaf certificate CN: %s doesn't match the given dns name: %s", leafCert.Subject.CommonName, dnsName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all the valid leafs to the certificates map so we can refer to them later
|
||||||
|
allCerts[leafID] = leafCert
|
||||||
|
|
||||||
|
// Retrieve all the trusted certificates that match this dns Name
|
||||||
|
certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(dnsName)
|
||||||
|
if err != nil {
|
||||||
|
// If the error that we get back is different than ErrNoCertificatesFound
|
||||||
|
// we couldn't check if there are any certificates with this CN already
|
||||||
|
// trusted. Let's take the conservative approach and not trust this key
|
||||||
|
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
|
||||||
|
logrus.Debugf("error retrieving certificates for: %s, %v", dnsName, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no certificates with this CN, lets TOFUS!
|
||||||
|
// Note that this logic should only exist in docker 1.8
|
||||||
|
if len(certsForCN) == 0 {
|
||||||
|
km.trustedCertificateStore.AddCert(leafCert)
|
||||||
|
certsForCN = append(certsForCN, leafCert)
|
||||||
|
logrus.Debugf("using TOFUS on %s with keyID: %s", dnsName, leafID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all known certificates for this CN and see if any are trusted
|
||||||
|
for _, cert := range certsForCN {
|
||||||
// Check to see if there is an exact match of this certificate.
|
// Check to see if there is an exact match of this certificate.
|
||||||
// Checking the CommonName is not required since ID is calculated over
|
certID, err := trustmanager.FingerprintCert(cert)
|
||||||
// Cert.Raw. It's included to prevent breaking logic with changes of how the
|
if err == nil && certID == leafID {
|
||||||
// ID gets computed.
|
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
|
||||||
_, err = km.trustedCertificateStore.GetCertificateByKeyID(leafID)
|
logrus.Debugf("found an exact match for %s with keyID: %s", dnsName, keyID)
|
||||||
if err == nil && leafCert.Subject.CommonName == dnsName {
|
}
|
||||||
certs[keyID] = rootSigned.Keys[keyID]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this leafCertificate has a chain to one of the Root CAs
|
// Check to see if this leafCertificate has a chain to one of the Root
|
||||||
// of our CA Store.
|
// CAs of our CA Store.
|
||||||
certList := []*x509.Certificate{leafCert}
|
err = trustmanager.Verify(km.trustedCAStore, dnsName, decodedCerts)
|
||||||
err = trustmanager.Verify(km.trustedCAStore, dnsName, certList)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
certs[keyID] = rootSigned.Keys[keyID]
|
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
|
||||||
|
logrus.Debugf("found a CA path for %s with keyID: %s", dnsName, keyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(certs) < 1 {
|
if len(validKeys) < 1 {
|
||||||
return errors.New("could not validate the path to a trusted root")
|
logrus.Debugf("wasn't able to trust any of the root keys")
|
||||||
|
return ErrValidationFail
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = signed.VerifyRoot(root, 0, certs, 1)
|
// TODO(david): change hardcoded minversion on TUF.
|
||||||
|
newRootKey, err := signed.VerifyRoot(root, 0, validKeys, 1)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyRoot returns a non-nil value if there is a root key rotation happening.
|
||||||
|
// If this happens, we should replace the old root of trust with the new one
|
||||||
|
if newRootKey != nil {
|
||||||
|
logrus.Debugf("got a new root key to rotate to: %s", newRootKey.ID())
|
||||||
|
|
||||||
|
// Retrieve the certificate associated with the new root key and trust it
|
||||||
|
newRootKeyCert, ok := allCerts[newRootKey.ID()]
|
||||||
|
// Paranoid check for the certificate still being in the map
|
||||||
|
if !ok {
|
||||||
|
logrus.Debugf("error while retrieving new root certificate with keyID: %s, %v", newRootKey.ID(), err)
|
||||||
|
return ErrRootRotationFail
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new root certificate to our certificate store
|
||||||
|
err := km.trustedCertificateStore.AddCert(newRootKeyCert)
|
||||||
|
if err != nil {
|
||||||
|
// Ignore the error if the certificate already exists
|
||||||
|
if _, ok := err.(*trustmanager.ErrCertExists); !ok {
|
||||||
|
logrus.Debugf("error while adding new root certificate with keyID: %s, %v", newRootKey.ID(), err)
|
||||||
|
return ErrRootRotationFail
|
||||||
|
}
|
||||||
|
logrus.Debugf("root certificate already exists in keystore: %s", newRootKey.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the new root certificate from the certificate mapping so we
|
||||||
|
// can remove trust from all of the remaining ones
|
||||||
|
delete(allCerts, newRootKey.ID())
|
||||||
|
|
||||||
|
// Iterate over all old valid certificates and remove them, essentially
|
||||||
|
// finishing the rotation of the currently trusted root certificate
|
||||||
|
for _, cert := range allCerts {
|
||||||
|
err := km.trustedCertificateStore.RemoveCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("error while removing old root certificate: %v", err)
|
||||||
|
return ErrRootRotationFail
|
||||||
|
}
|
||||||
|
logrus.Debugf("removed trust from old root certificate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Root validation succeeded")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -162,3 +162,8 @@ func listKeys(s LimitedFileStore) []string {
|
||||||
}
|
}
|
||||||
return keyIDList
|
return keyIDList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveKey removes the key from the keyfilestore
|
||||||
|
func (s *KeyFileStore) RemoveKey(name string) error {
|
||||||
|
return s.Remove(name)
|
||||||
|
}
|
||||||
|
|
|
@ -251,3 +251,53 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) {
|
||||||
t.Fatalf("expected error while decrypting the content due to invalid passphrase")
|
t.Fatalf("expected error while decrypting the content due to invalid passphrase")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveKey(t *testing.T) {
|
||||||
|
testName := "docker.com/notary/root"
|
||||||
|
testExt := "key"
|
||||||
|
|
||||||
|
// Temporary directory where test files will be created
|
||||||
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempBaseDir)
|
||||||
|
|
||||||
|
// Since we're generating this manually we need to add the extension '.'
|
||||||
|
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
|
||||||
|
|
||||||
|
// Create our store
|
||||||
|
store, err := NewKeyFileStore(tempBaseDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create new key filestore: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := GenerateRSAKey(rand.Reader, 512)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not generate private key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the AddKey function
|
||||||
|
err = store.AddKey(testName, privKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to add file to store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if file exists
|
||||||
|
_, err = ioutil.ReadFile(expectedFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected file not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call remove key
|
||||||
|
err = store.RemoveKey(testName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to remove key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if file still exists
|
||||||
|
_, err = ioutil.ReadFile(expectedFilePath)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("file should not exist %s", expectedFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -50,14 +50,14 @@ func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCert creates a filename for a given cert and adds a certificate with that name
|
// AddCert creates a filename for a given cert and adds a certificate with that name
|
||||||
func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
func (s *X509FileStore) AddCert(cert *x509.Certificate) error {
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return errors.New("adding nil Certificate to X509Store")
|
return errors.New("adding nil Certificate to X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this certificate meets our validation criteria
|
// Check if this certificate meets our validation criteria
|
||||||
if !s.validate.Validate(cert) {
|
if !s.validate.Validate(cert) {
|
||||||
return errors.New("certificate validation failed")
|
return &ErrCertValidation{}
|
||||||
}
|
}
|
||||||
// Attempt to write the certificate to the file
|
// Attempt to write the certificate to the file
|
||||||
if err := s.addNamedCert(cert); err != nil {
|
if err := s.addNamedCert(cert); err != nil {
|
||||||
|
@ -69,16 +69,16 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
|
||||||
|
|
||||||
// addNamedCert allows adding a certificate while controling the filename it gets
|
// addNamedCert allows adding a certificate while controling the filename it gets
|
||||||
// stored under. If the file does not exist on disk, saves it.
|
// stored under. If the file does not exist on disk, saves it.
|
||||||
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
fileName, keyID, err := fileName(cert)
|
fileName, certID, err := fileName(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debug("Adding cert with keyID: ", keyID)
|
logrus.Debug("Adding cert with certID: ", certID)
|
||||||
// Validate if we already loaded this certificate before
|
// Validate if we already added this certificate before
|
||||||
if _, ok := s.fingerprintMap[keyID]; ok {
|
if _, ok := s.fingerprintMap[certID]; ok {
|
||||||
return errors.New("certificate already in the store")
|
return &ErrCertExists{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert certificate to PEM
|
// Convert certificate to PEM
|
||||||
|
@ -98,28 +98,28 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
// We wrote the certificate succcessfully, add it to our in-memory storage
|
||||||
s.fingerprintMap[keyID] = cert
|
s.fingerprintMap[certID] = cert
|
||||||
s.fileMap[keyID] = fileName
|
s.fileMap[certID] = fileName
|
||||||
|
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.Subject.CommonName)
|
||||||
s.nameMap[name] = append(s.nameMap[name], keyID)
|
s.nameMap[name] = append(s.nameMap[name], certID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveCert removes a certificate from a X509FileStore.
|
// RemoveCert removes a certificate from a X509FileStore.
|
||||||
func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
func (s *X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return errors.New("removing nil Certificate from X509Store")
|
return errors.New("removing nil Certificate from X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err := fingerprintCert(cert)
|
certID, err := fingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
delete(s.fingerprintMap, keyID)
|
delete(s.fingerprintMap, certID)
|
||||||
filename := s.fileMap[keyID]
|
filename := s.fileMap[certID]
|
||||||
delete(s.fileMap, keyID)
|
delete(s.fileMap, certID)
|
||||||
|
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
fpList := s.nameMap[name]
|
fpList := s.nameMap[name]
|
||||||
newfpList := fpList[:0]
|
newfpList := fpList[:0]
|
||||||
for _, x := range fpList {
|
for _, x := range fpList {
|
||||||
if x != keyID {
|
if x != certID {
|
||||||
newfpList = append(newfpList, x)
|
newfpList = append(newfpList, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,20 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAll removes all the certificates from the store
|
||||||
|
func (s *X509FileStore) RemoveAll() error {
|
||||||
|
for _, filename := range s.fileMap {
|
||||||
|
if err := s.fileStore.Remove(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.fileMap = make(map[CertID]string)
|
||||||
|
s.fingerprintMap = make(map[CertID]*x509.Certificate)
|
||||||
|
s.nameMap = make(map[string][]CertID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning
|
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning
|
||||||
// an error if no Certificates are found
|
// an error if no Certificates are found
|
||||||
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
||||||
|
@ -152,7 +166,7 @@ func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
||||||
func (s X509FileStore) AddCertFromFile(filename string) error {
|
func (s *X509FileStore) AddCertFromFile(filename string) error {
|
||||||
cert, err := LoadCertFromFile(filename)
|
cert, err := LoadCertFromFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -162,7 +176,7 @@ func (s X509FileStore) AddCertFromFile(filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificates returns an array with all of the current X509 Certificates.
|
// GetCertificates returns an array with all of the current X509 Certificates.
|
||||||
func (s X509FileStore) GetCertificates() []*x509.Certificate {
|
func (s *X509FileStore) GetCertificates() []*x509.Certificate {
|
||||||
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
||||||
i := 0
|
i := 0
|
||||||
for _, v := range s.fingerprintMap {
|
for _, v := range s.fingerprintMap {
|
||||||
|
@ -174,7 +188,7 @@ func (s X509FileStore) GetCertificates() []*x509.Certificate {
|
||||||
|
|
||||||
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
||||||
// in the store.
|
// in the store.
|
||||||
func (s X509FileStore) GetCertificatePool() *x509.CertPool {
|
func (s *X509FileStore) GetCertificatePool() *x509.CertPool {
|
||||||
pool := x509.NewCertPool()
|
pool := x509.NewCertPool()
|
||||||
|
|
||||||
for _, v := range s.fingerprintMap {
|
for _, v := range s.fingerprintMap {
|
||||||
|
@ -183,25 +197,52 @@ func (s X509FileStore) GetCertificatePool() *x509.CertPool {
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
|
// GetCertificateByCertID returns the certificate that matches a certain certID
|
||||||
func (s X509FileStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
|
func (s *X509FileStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
|
||||||
|
return s.getCertificateByCertID(CertID(certID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCertificateByCertID returns the certificate that matches a certain certID
|
||||||
|
func (s *X509FileStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
// If it does not look like a hex encoded sha256 hash, error
|
||||||
if len(keyID) != 64 {
|
if len(certID) != 64 {
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
return nil, errors.New("invalid Subject Key Identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
// Check to see if this subject key identifier exists
|
||||||
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok {
|
if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("certificate not found in Key Store")
|
return nil, &ErrNoCertificatesFound{query: string(certID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificatesByCN returns all the certificates that match a specific
|
||||||
|
// CommonName
|
||||||
|
func (s *X509FileStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
|
||||||
|
var certs []*x509.Certificate
|
||||||
|
if ids, ok := s.nameMap[cn]; ok {
|
||||||
|
for _, v := range ids {
|
||||||
|
cert, err := s.getCertificateByCertID(v)
|
||||||
|
if err != nil {
|
||||||
|
// This error should never happen. This would mean that we have
|
||||||
|
// an inconsistent X509FileStore
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(certs) == 0 {
|
||||||
|
return nil, &ErrNoCertificatesFound{query: cn}
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
||||||
// as part of the roots list. This never allows the use of system roots, returning
|
// as part of the roots list. This never allows the use of system roots, returning
|
||||||
// an error if there are no root CAs.
|
// an error if there are no root CAs.
|
||||||
func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
func (s *X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
||||||
// If we have no Certificates loaded return error (we don't want to rever to using
|
// If we have no Certificates loaded return error (we don't want to rever to using
|
||||||
// system CAs).
|
// system CAs).
|
||||||
if len(s.fingerprintMap) == 0 {
|
if len(s.fingerprintMap) == 0 {
|
||||||
|
@ -217,10 +258,10 @@ func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileName(cert *x509.Certificate) (string, CertID, error) {
|
func fileName(cert *x509.Certificate) (string, CertID, error) {
|
||||||
keyID, err := fingerprintCert(cert)
|
certID, err := fingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.Join(cert.Subject.CommonName, string(keyID)), keyID, nil
|
return path.Join(cert.Subject.CommonName, string(certID)), certID, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,52 @@ func TestRemoveCertX509FileStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveAllX509FileStore(t *testing.T) {
|
||||||
|
tempDir, err := ioutil.TempDir("", "cert-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add three certificates to store
|
||||||
|
store, _ := NewX509FileStore(tempDir)
|
||||||
|
certFiles := [3]string{"../fixtures/root-ca.crt",
|
||||||
|
"../fixtures/intermediate-ca.crt",
|
||||||
|
"../fixtures/secure.example.com.crt"}
|
||||||
|
for _, file := range certFiles {
|
||||||
|
b, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't load fixture: %v", err)
|
||||||
|
}
|
||||||
|
var block *pem.Block
|
||||||
|
block, _ = pem.Decode(b)
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
err = store.AddCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load certificate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of certificates should be 3 since we added the cert
|
||||||
|
numCerts := len(store.GetCertificates())
|
||||||
|
if numCerts != 3 {
|
||||||
|
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the cert from the store
|
||||||
|
err = store.RemoveAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to remove all certificates: %v", err)
|
||||||
|
}
|
||||||
|
// Number of certificates should be 0 since we added and removed the cert
|
||||||
|
numCerts = len(store.GetCertificates())
|
||||||
|
if numCerts != 0 {
|
||||||
|
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
tempDir, err := ioutil.TempDir("", "cert-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -146,7 +192,7 @@ func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
t.Fatalf("failed to load certificate from file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("no error returned for inexistent certificate")
|
t.Fatalf("no error returned for inexistent certificate")
|
||||||
}
|
}
|
||||||
|
@ -181,7 +227,7 @@ func TestGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to retrieve cert by Subject Key IDs
|
// Tries to retrieve cert by Subject Key IDs
|
||||||
_, err = store.GetCertificateByKeyID(keyID)
|
_, err = store.GetCertificateByCertID(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected certificate in store: %s", keyID)
|
t.Fatalf("expected certificate in store: %s", keyID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package trustmanager
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// X509MemStore implements X509Store as an in-memory object with no persistence
|
// X509MemStore implements X509Store as an in-memory object with no persistence
|
||||||
|
@ -23,7 +25,7 @@ func NewX509MemStore() *X509MemStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewX509FilteredMemStore returns a new X509FileStore that validates certificates
|
// NewX509FilteredMemStore returns a new X509Memstore that validates certificates
|
||||||
// that are added.
|
// that are added.
|
||||||
func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore {
|
func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore {
|
||||||
s := &X509MemStore{
|
s := &X509MemStore{
|
||||||
|
@ -37,45 +39,48 @@ func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStor
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCert adds a certificate to the store
|
// AddCert adds a certificate to the store
|
||||||
func (s X509MemStore) AddCert(cert *x509.Certificate) error {
|
func (s *X509MemStore) AddCert(cert *x509.Certificate) error {
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return errors.New("adding nil Certificate to X509Store")
|
return errors.New("adding nil Certificate to X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.validate.Validate(cert) {
|
if !s.validate.Validate(cert) {
|
||||||
return errors.New("certificate failed validation")
|
return &ErrCertValidation{}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err := fingerprintCert(cert)
|
certID, err := fingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fingerprintMap[keyID] = cert
|
logrus.Debug("Adding cert with certID: ", certID)
|
||||||
|
|
||||||
|
// In this store we overwrite the certificate if it already exists
|
||||||
|
s.fingerprintMap[certID] = cert
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
s.nameMap[name] = append(s.nameMap[name], keyID)
|
s.nameMap[name] = append(s.nameMap[name], certID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveCert removes a certificate from a X509MemStore.
|
// RemoveCert removes a certificate from a X509MemStore.
|
||||||
func (s X509MemStore) RemoveCert(cert *x509.Certificate) error {
|
func (s *X509MemStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
if cert == nil {
|
if cert == nil {
|
||||||
return errors.New("removing nil Certificate to X509Store")
|
return errors.New("removing nil Certificate to X509Store")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err := fingerprintCert(cert)
|
certID, err := fingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
delete(s.fingerprintMap, keyID)
|
delete(s.fingerprintMap, certID)
|
||||||
name := string(cert.RawSubject)
|
name := string(cert.RawSubject)
|
||||||
|
|
||||||
// Filter the fingerprint out of this name entry
|
// Filter the fingerprint out of this name entry
|
||||||
fpList := s.nameMap[name]
|
fpList := s.nameMap[name]
|
||||||
newfpList := fpList[:0]
|
newfpList := fpList[:0]
|
||||||
for _, x := range fpList {
|
for _, x := range fpList {
|
||||||
if x != keyID {
|
if x != certID {
|
||||||
newfpList = append(newfpList, x)
|
newfpList = append(newfpList, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +89,20 @@ func (s X509MemStore) RemoveCert(cert *x509.Certificate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAll removes all the certificates from the store
|
||||||
|
func (s *X509MemStore) RemoveAll() error {
|
||||||
|
|
||||||
|
for _, cert := range s.fingerprintMap {
|
||||||
|
if err := s.RemoveCert(cert); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddCertFromPEM adds a certificate to the store from a PEM blob
|
// AddCertFromPEM adds a certificate to the store from a PEM blob
|
||||||
func (s X509MemStore) AddCertFromPEM(pemBytes []byte) error {
|
func (s *X509MemStore) AddCertFromPEM(pemBytes []byte) error {
|
||||||
cert, err := LoadCertFromPEM(pemBytes)
|
cert, err := LoadCertFromPEM(pemBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -94,7 +111,7 @@ func (s X509MemStore) AddCertFromPEM(pemBytes []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
||||||
func (s X509MemStore) AddCertFromFile(originFilname string) error {
|
func (s *X509MemStore) AddCertFromFile(originFilname string) error {
|
||||||
cert, err := LoadCertFromFile(originFilname)
|
cert, err := LoadCertFromFile(originFilname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -104,7 +121,7 @@ func (s X509MemStore) AddCertFromFile(originFilname string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificates returns an array with all of the current X509 Certificates.
|
// GetCertificates returns an array with all of the current X509 Certificates.
|
||||||
func (s X509MemStore) GetCertificates() []*x509.Certificate {
|
func (s *X509MemStore) GetCertificates() []*x509.Certificate {
|
||||||
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
||||||
i := 0
|
i := 0
|
||||||
for _, v := range s.fingerprintMap {
|
for _, v := range s.fingerprintMap {
|
||||||
|
@ -116,7 +133,7 @@ func (s X509MemStore) GetCertificates() []*x509.Certificate {
|
||||||
|
|
||||||
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
||||||
// in the store.
|
// in the store.
|
||||||
func (s X509MemStore) GetCertificatePool() *x509.CertPool {
|
func (s *X509MemStore) GetCertificatePool() *x509.CertPool {
|
||||||
pool := x509.NewCertPool()
|
pool := x509.NewCertPool()
|
||||||
|
|
||||||
for _, v := range s.fingerprintMap {
|
for _, v := range s.fingerprintMap {
|
||||||
|
@ -125,25 +142,52 @@ func (s X509MemStore) GetCertificatePool() *x509.CertPool {
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
|
// GetCertificateByCertID returns the certificate that matches a certain certID
|
||||||
func (s X509MemStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
|
func (s *X509MemStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
|
||||||
|
return s.getCertificateByCertID(CertID(certID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCertificateByCertID returns the certificate that matches a certain certID or error
|
||||||
|
func (s *X509MemStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
// If it does not look like a hex encoded sha256 hash, error
|
||||||
if len(keyID) != 64 {
|
if len(certID) != 64 {
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
return nil, errors.New("invalid Subject Key Identifier")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
// Check to see if this subject key identifier exists
|
||||||
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok {
|
if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
|
||||||
return cert, nil
|
return cert, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("certificate not found in Key Store")
|
return nil, &ErrNoCertificatesFound{query: string(certID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificatesByCN returns all the certificates that match a specific
|
||||||
|
// CommonName
|
||||||
|
func (s *X509MemStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
|
||||||
|
var certs []*x509.Certificate
|
||||||
|
if ids, ok := s.nameMap[cn]; ok {
|
||||||
|
for _, v := range ids {
|
||||||
|
cert, err := s.getCertificateByCertID(v)
|
||||||
|
if err != nil {
|
||||||
|
// This error should never happen. This would mean that we have
|
||||||
|
// an inconsistent X509MemStore
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(certs) == 0 {
|
||||||
|
return nil, &ErrNoCertificatesFound{query: cn}
|
||||||
|
}
|
||||||
|
|
||||||
|
return certs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
||||||
// as part of the roots list. This never allows the use of system roots, returning
|
// as part of the roots list. This never allows the use of system roots, returning
|
||||||
// an error if there are no root CAs.
|
// an error if there are no root CAs.
|
||||||
func (s X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
func (s *X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
||||||
// If we have no Certificates loaded return error (we don't want to rever to using
|
// If we have no Certificates loaded return error (we don't want to rever to using
|
||||||
// system CAs).
|
// system CAs).
|
||||||
if len(s.fingerprintMap) == 0 {
|
if len(s.fingerprintMap) == 0 {
|
||||||
|
|
|
@ -106,14 +106,55 @@ func TestRemoveCert(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInexistentGetCertificateByKeyID(t *testing.T) {
|
func TestRemoveAllX509MemStore(t *testing.T) {
|
||||||
|
// Add three certificates to store
|
||||||
|
store := NewX509MemStore()
|
||||||
|
certFiles := [3]string{"../fixtures/root-ca.crt",
|
||||||
|
"../fixtures/intermediate-ca.crt",
|
||||||
|
"../fixtures/secure.example.com.crt"}
|
||||||
|
for _, file := range certFiles {
|
||||||
|
b, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't load fixture: %v", err)
|
||||||
|
}
|
||||||
|
var block *pem.Block
|
||||||
|
block, _ = pem.Decode(b)
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("couldn't parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
err = store.AddCert(cert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load certificate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of certificates should be 3 since we added the cert
|
||||||
|
numCerts := len(store.GetCertificates())
|
||||||
|
if numCerts != 3 {
|
||||||
|
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the cert from the store
|
||||||
|
err := store.RemoveAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to remove all certificates: %v", err)
|
||||||
|
}
|
||||||
|
// Number of certificates should be 0 since we added and removed the cert
|
||||||
|
numCerts = len(store.GetCertificates())
|
||||||
|
if numCerts != 0 {
|
||||||
|
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestInexistentGetCertificateByCertID(t *testing.T) {
|
||||||
store := NewX509MemStore()
|
store := NewX509MemStore()
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
t.Fatalf("failed to load certificate from file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("no error returned for inexistent certificate")
|
t.Fatalf("no error returned for inexistent certificate")
|
||||||
}
|
}
|
||||||
|
@ -138,15 +179,15 @@ func TestGetCertificateByKeyID(t *testing.T) {
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
t.Fatalf("failed to load certificate from PEM: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyID, err := FingerprintCert(cert)
|
certID, err := FingerprintCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to fingerprint the certificate: %v", err)
|
t.Fatalf("failed to fingerprint the certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to retrieve cert by Subject Key IDs
|
// Tries to retrieve cert by Subject Key IDs
|
||||||
_, err = store.GetCertificateByKeyID(keyID)
|
_, err = store.GetCertificateByCertID(certID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected certificate in store: %s", keyID)
|
t.Fatalf("expected certificate in store: %s", certID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,47 @@ import (
|
||||||
|
|
||||||
const certExtension string = "crt"
|
const certExtension string = "crt"
|
||||||
|
|
||||||
|
// ErrNoCertificatesFound is returned when no certificates are found for a
|
||||||
|
// GetCertificatesBy*
|
||||||
|
type ErrNoCertificatesFound struct {
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoCertificatesFound is returned when no certificates are found for a
|
||||||
|
// GetCertificatesBy*
|
||||||
|
func (err ErrNoCertificatesFound) Error() string {
|
||||||
|
return fmt.Sprintf("error, no certificates found in the keystore match: %s", err.query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCertValidation is returned when a certificate doesn't pass the store specific
|
||||||
|
// validations
|
||||||
|
type ErrCertValidation struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCertValidation is returned when a certificate doesn't pass the store specific
|
||||||
|
// validations
|
||||||
|
func (err ErrCertValidation) Error() string {
|
||||||
|
return fmt.Sprintf("store-specific certificate validations failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCertExists is returned when a Certificate already exists in the key store
|
||||||
|
type ErrCertExists struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrCertExists is returned when a Certificate already exists in the key store
|
||||||
|
func (err ErrCertExists) Error() string {
|
||||||
|
return fmt.Sprintf("certificate already in the store")
|
||||||
|
}
|
||||||
|
|
||||||
// X509Store is the interface for all X509Stores
|
// X509Store is the interface for all X509Stores
|
||||||
type X509Store interface {
|
type X509Store interface {
|
||||||
AddCert(cert *x509.Certificate) error
|
AddCert(cert *x509.Certificate) error
|
||||||
AddCertFromPEM(pemCerts []byte) error
|
AddCertFromPEM(pemCerts []byte) error
|
||||||
AddCertFromFile(filename string) error
|
AddCertFromFile(filename string) error
|
||||||
RemoveCert(cert *x509.Certificate) error
|
RemoveCert(cert *x509.Certificate) error
|
||||||
GetCertificateByKeyID(keyID string) (*x509.Certificate, error)
|
RemoveAll() error
|
||||||
|
GetCertificateByCertID(certID string) (*x509.Certificate, error)
|
||||||
|
GetCertificatesByCN(cn string) ([]*x509.Certificate, error)
|
||||||
GetCertificates() []*x509.Certificate
|
GetCertificates() []*x509.Certificate
|
||||||
GetCertificatePool() *x509.CertPool
|
GetCertificatePool() *x509.CertPool
|
||||||
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
||||||
|
|
|
@ -127,25 +127,66 @@ func loadCertsFromDir(s *X509FileStore) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadCertFromFile tries to adds a X509 certificate to the store given a filename
|
// LoadCertFromFile loads the first certificate from the file provided. The
|
||||||
|
// data is expected to be PEM Encoded and contain one of more certificates
|
||||||
|
// with PEM type "CERTIFICATE"
|
||||||
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
|
||||||
// TODO(diogo): handle multiple certificates in one file.
|
certs, err := LoadCertBundleFromFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return certs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCertBundleFromFile loads certificates from the []byte provided. The
|
||||||
|
// data is expected to be PEM Encoded and contain one of more certificates
|
||||||
|
// with PEM type "CERTIFICATE"
|
||||||
|
func LoadCertBundleFromFile(filename string) ([]*x509.Certificate, error) {
|
||||||
b, err := ioutil.ReadFile(filename)
|
b, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return LoadCertBundleFromPEM(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCertBundleFromPEM loads certificates from the []byte provided. The
|
||||||
|
// data is expected to be PEM Encoded and contain one of more certificates
|
||||||
|
// with PEM type "CERTIFICATE"
|
||||||
|
func LoadCertBundleFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
|
||||||
|
certificates := []*x509.Certificate{}
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
block, b = pem.Decode(b)
|
block, pemBytes = pem.Decode(pemBytes)
|
||||||
for ; block != nil; block, b = pem.Decode(b) {
|
for ; block != nil; block, pemBytes = pem.Decode(pemBytes) {
|
||||||
if block.Type == "CERTIFICATE" {
|
if block.Type == "CERTIFICATE" {
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
return cert, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
certificates = append(certificates, cert)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("could not load certificate from file")
|
if len(certificates) == 0 {
|
||||||
|
return nil, fmt.Errorf("no valid certificates found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLeafCerts parses a list of x509 Certificates and returns all of them
|
||||||
|
// that aren't CA
|
||||||
|
func GetLeafCerts(certs []*x509.Certificate) []*x509.Certificate {
|
||||||
|
var leafCerts []*x509.Certificate
|
||||||
|
for _, cert := range certs {
|
||||||
|
if cert.IsCA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
leafCerts = append(leafCerts, cert)
|
||||||
|
}
|
||||||
|
return leafCerts
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
|
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
|
||||||
|
|
Loading…
Reference in New Issue