diff options
| author | Jeremy Clerc <jclerc@google.com> | 2017-02-14 15:28:58 +0100 |
|---|---|---|
| committer | Jeremy Clerc <jclerc@google.com> | 2017-02-14 15:28:58 +0100 |
| commit | aee8c433051c70bd42d6550a38a11c2830b1b223 (patch) | |
| tree | aa384b0e6823793b72edde767eed7ab457dcf2e0 | |
| parent | ccb8768272387252f897e3b216afd16e34d46032 (diff) | |
| download | easypki-aee8c433051c70bd42d6550a38a11c2830b1b223.tar.xz | |
Add bolt store backend.
| -rw-r--r-- | pkg/easypki/easypki_test.go | 30 | ||||
| -rw-r--r-- | pkg/store/bolt.go | 184 | ||||
| -rw-r--r-- | pkg/store/bolt_test.go | 212 |
3 files changed, 421 insertions, 5 deletions
diff --git a/pkg/easypki/easypki_test.go b/pkg/easypki/easypki_test.go index 3c839dc..2cc2492 100644 --- a/pkg/easypki/easypki_test.go +++ b/pkg/easypki/easypki_test.go @@ -19,23 +19,43 @@ import ( "crypto/x509/pkix" "io/ioutil" "net" + "os" "testing" "time" - "reflect" - + "github.com/boltdb/bolt" "github.com/google/easypki/pkg/store" + + "reflect" ) -func TestE2E(t *testing.T) { +func TestLocalE2E(t *testing.T) { root, err := ioutil.TempDir("", "testeasypki") if err != nil { t.Fatalf("failed creating temporary directory: %v", err) } - //defer os.RemoveAll(root) + defer os.RemoveAll(root) + + E2E(t, &EasyPKI{Store: &store.Local{Root: root}}) +} - pki := &EasyPKI{Store: &store.Local{Root: root}} +func TestBoltE2E(t *testing.T) { + f, err := ioutil.TempFile("", "boltdb") + if err != nil { + t.Fatalf("failed creating tempfile for boltdb: %v", err) + } + defer os.Remove(f.Name()) + db, err := bolt.Open(f.Name(), 0600, nil) + if err != nil { + t.Fatalf("failed opening temp boltdb: %v", err) + } + defer db.Close() + E2E(t, &EasyPKI{Store: &store.Bolt{DB: db}}) +} +// E2E provides a frameweork to run tests end to end using a different +// store backend. +func E2E(t *testing.T, pki *EasyPKI) { commonSubject := pkix.Name{ Organization: []string{"Acme Inc."}, OrganizationalUnit: []string{"IT"}, diff --git a/pkg/store/bolt.go b/pkg/store/bolt.go new file mode 100644 index 0000000..381a18b --- /dev/null +++ b/pkg/store/bolt.go @@ -0,0 +1,184 @@ +// Copyright 2015 Google Inc. +// +// 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 store + +import ( + "crypto/x509/pkix" + "errors" + "math/big" + "time" + + "fmt" + + "github.com/boltdb/bolt" + "github.com/google/easypki/pkg/certificate" +) + +var ( + keysBucketKey = []byte("keys") + certsBucketKey = []byte("certs") + revokedBucketKey = []byte("revoked") +) + +// Errors. +var ( + ErrDoesNotExist = errors.New("does not exist") +) + +// Bolt lets us store a Certificate Authority in a Bolt DB. +// +// Certificate bundles are stored per CA bucket, and each of them has a keys +// bucket and a certs buckets. +type Bolt struct { + DB *bolt.DB +} + +// Add adds the given bundle to the database. +func (b *Bolt) Add(caName, name string, isCa bool, key, cert []byte) error { + return b.DB.Update(func(tx *bolt.Tx) error { + kb, cb, err := buckets(tx, caName) + if err != nil { + return err + } + k := []byte(name) + if err := kb.Put(k, key); err != nil { + return fmt.Errorf("failed puting %v key into %v keys bucket: %v", name, caName, err) + } + if err := cb.Put(k, cert); err != nil { + return fmt.Errorf("failed puting %v cert into %v certs bucket: %v", name, caName, err) + } + + if isCa && name != caName { + kb, cb, err = buckets(tx, name) + if err != nil { + return err + } + if err := kb.Put(k, key); err != nil { + return fmt.Errorf("failed puting %v key into %v keys bucket: %v", name, name, err) + } + if err := cb.Put(k, cert); err != nil { + return fmt.Errorf("failed puting %v cert into %v certs bucket: %v", name, name, err) + } + } + return nil + }) +} + +// Fetch fetchs the private key and certificate for a given name signed by caName. +func (b *Bolt) Fetch(caName, name string) ([]byte, []byte, error) { + var key, cert []byte + err := b.DB.View(func(tx *bolt.Tx) error { + rb := tx.Bucket([]byte(caName)) + if rb == nil { + return fmt.Errorf("%v bucket does not exist", caName) + } + kb := rb.Bucket(keysBucketKey) + if kb == nil { + return fmt.Errorf("%v keys bucket does not exist", caName) + } + cb := rb.Bucket(certsBucketKey) + if cb == nil { + return fmt.Errorf("%v certs bucket does not exist", caName) + } + k := []byte(name) + key = kb.Get(k) + cert = cb.Get(k) + if key == nil || cert == nil { + return ErrDoesNotExist + } + return nil + }) + return key, cert, err +} + +// Update updates the state of a given certificate in the index.txt. +func (b *Bolt) Update(caName string, sn *big.Int, st certificate.State) error { + if st != certificate.Revoked { + return fmt.Errorf("unsupported update for certificate state %v", st) + } + return b.DB.Update(func(tx *bolt.Tx) error { + root, err := tx.CreateBucketIfNotExists([]byte(caName)) + if err != nil { + return fmt.Errorf("failed getting %v bucket: %v", caName, err) + } + revoked, err := root.CreateBucketIfNotExists(revokedBucketKey) + if err != nil { + return fmt.Errorf("failed getting %v revoked bucket: %v", root, err) + } + t, err := time.Now().GobEncode() + if err != nil { + return fmt.Errorf("failed gob encoding current time: %v", err) + } + k, err := sn.GobEncode() + if err != nil { + return fmt.Errorf("failed gob encoding serial number %v: %v", sn, err) + } + if err := revoked.Put(k, t); err != nil { + return fmt.Errorf("failed adding serial number %v to the %v revoked bucket: %v", sn, caName, err) + } + return nil + }) +} + +// Revoked returns a list of revoked certificates. +func (b *Bolt) Revoked(caName string) ([]pkix.RevokedCertificate, error) { + var revokedCerts []pkix.RevokedCertificate + if err := b.DB.View(func(tx *bolt.Tx) error { + root := tx.Bucket([]byte(caName)) + if root == nil { + return fmt.Errorf("%v bucket does not exist", caName) + } + revoked := root.Bucket(revokedBucketKey) + if revoked == nil { + return nil + } + return revoked.ForEach(func(k, v []byte) error { + sn := big.NewInt(0) + if err := sn.GobDecode(k); err != nil { + return fmt.Errorf("failed gob decoding serial number: %v", err) + } + t := time.Time{} + if err := t.GobDecode(v); err != nil { + return fmt.Errorf("failed gob decoding revocation time: %v", err) + } + revokedCerts = append(revokedCerts, pkix.RevokedCertificate{ + SerialNumber: sn, + RevocationTime: t, + }) + return nil + }) + }); err != nil { + return nil, err + } + return revokedCerts, nil +} + +// buckets returns respectively the keys and certs buckets nested below the +// given root bucket. +func buckets(tx *bolt.Tx, root string) (*bolt.Bucket, *bolt.Bucket, error) { + rb, err := tx.CreateBucketIfNotExists([]byte(root)) + if err != nil { + return nil, nil, fmt.Errorf("failed getting %v bucket: %v", root, err) + } + kb, err := rb.CreateBucketIfNotExists(keysBucketKey) + if err != nil { + return nil, nil, fmt.Errorf("failed getting %v keys bucket: %v", root, err) + } + cb, err := rb.CreateBucketIfNotExists(certsBucketKey) + if err != nil { + return nil, nil, fmt.Errorf("failed getting %v certs bucket: %v", root, err) + } + return kb, cb, nil +} diff --git a/pkg/store/bolt_test.go b/pkg/store/bolt_test.go new file mode 100644 index 0000000..b5f7758 --- /dev/null +++ b/pkg/store/bolt_test.go @@ -0,0 +1,212 @@ +// Copyright 2015 Google Inc. +// +// 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 store + +import ( + "fmt" + "io/ioutil" + "math/big" + "os" + "testing" + + "reflect" + + "github.com/boltdb/bolt" + "github.com/google/easypki/pkg/certificate" +) + +func TestBolt(t *testing.T) { + f, err := ioutil.TempFile("", "boltdb") + if err != nil { + t.Fatalf("failed creating tempfile for boltdb: %v", err) + } + defer os.Remove(f.Name()) + db, err := bolt.Open(f.Name(), 0600, nil) + if err != nil { + t.Fatalf("failed opening temp boltdb: %v", err) + } + defer db.Close() + b := &Bolt{DB: db} + + var ( + fakeKeyValue = []byte("fakeKeyValue") + fakeCertValue = []byte("fakeCertValue") + ) + if err := b.Add("rootCA", "rootCA", true, fakeKeyValue, fakeCertValue); err != nil { + t.Errorf("Add(rootCA, rootCA): got error %v != expected nil", err) + } + + if err := b.Add("rootCA", "intermediateCA", true, fakeKeyValue, fakeCertValue); err != nil { + t.Errorf("Add(rootCA, intermediateCA): got error %v != expected nil", err) + } + if err := b.Add("intermediateCA", "somecert", false, fakeKeyValue, fakeCertValue); err != nil { + t.Errorf("Add(intermediateCA, somecert): got error %v != expected nil", err) + } + + expectedKeys := []struct { + bucket string + key string + }{ + {"rootCA", "rootCA"}, + {"rootCA", "intermediateCA"}, + {"intermediateCA", "intermediateCA"}, + {"intermediateCA", "somecert"}, + } + // Using Update instead of View so we can use buckets() as a shortcut to + // the buckets reference. + if err := db.Update(func(tx *bolt.Tx) error { + for _, key := range expectedKeys { + kb, cb, err := buckets(tx, key.bucket) + if err != nil { + t.Errorf("buckets(%v): got error %v != expected nil", key.bucket, err) + continue + } + k := []byte(key.key) + if kb.Get(k) == nil { + t.Errorf("(%v keys bucket).Get(%v): not found", key.bucket, key.key) + } + if cb.Get(k) == nil { + t.Errorf("(%v certs bucket).Get(%v): not found", key.bucket, key.key) + } + } + return nil + }); err != nil { + t.Errorf("failed checking keys existence: %v", err) + } + + k, c, err := b.Fetch("intermediateCA", "somecert") + if err != nil { + t.Errorf("Fetch(intermediateCA, somecert): got error %v != expected nil", err) + } + if !reflect.DeepEqual(k, fakeKeyValue) { + t.Errorf("Fetch(intermediateCA, somecert): got key value %v != expected %v", k, fakeKeyValue) + } + if !reflect.DeepEqual(c, fakeCertValue) { + t.Errorf("Fetch(intermediateCA, somecert): got cert value %v != expected %v", c, fakeCertValue) + } + + _, _, err = b.Fetch("dummyCA", "dummyCA") + if err == nil || err.Error() != "dummyCA bucket does not exist" { + t.Errorf("Fetch(dummyCA, dummyCA): got error %v != expected dummyCA bucket does not exist", err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucket([]byte("dummyCA")) + return err + }); err != nil { + t.Errorf("failed creating dummyCA bucket: %v", err) + } + + _, _, err = b.Fetch("dummyCA", "dummyCA") + if err == nil || err.Error() != "dummyCA keys bucket does not exist" { + t.Errorf("Fetch(dummyCA, dummyCA): got error %v != expected dummyCA keys bucket does not exist", err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + caBucket := tx.Bucket([]byte("dummyCA")) + if caBucket == nil { + return fmt.Errorf("missing dummyCA bucket") + } + _, err := caBucket.CreateBucket(keysBucketKey) + return err + }); err != nil { + t.Errorf("failed creating dummyCA keys bucket: %v", err) + } + + _, _, err = b.Fetch("dummyCA", "dummyCA") + if err == nil || err.Error() != "dummyCA certs bucket does not exist" { + t.Errorf("Fetch(dummyCA, dummyCA): got error %v != expected dummyCA certs bucket does not exist", err) + } + + if err := db.Update(func(tx *bolt.Tx) error { + caBucket := tx.Bucket([]byte("dummyCA")) + if caBucket == nil { + return fmt.Errorf("missing dummyCA bucket") + } + _, err := caBucket.CreateBucket(certsBucketKey) + return err + }); err != nil { + t.Errorf("failed creating dummyCA certs bucket: %v", err) + } + _, _, err = b.Fetch("dummyCA", "dummyCA") + if err == nil || err != ErrDoesNotExist { + t.Errorf("Fetch(dummyCA, dummyCA): got error %v != %v", err, ErrDoesNotExist) + } + + sn := big.NewInt(101) + if err := b.Update("intermediateCA", sn, certificate.Expired); err == nil { + t.Errorf("Update(intermediateCA, 101, Expired): got error nil != expected %v %v", "unsupported update for certificate state", certificate.Expired) + } + if err := b.Update("intermediateCA", sn, certificate.Revoked); err != nil { + t.Errorf("Update(intermediateCA, 101, Revoked): got error %v != expected nil", err) + } + if err := b.DB.View(func(tx *bolt.Tx) error { + caBucket := tx.Bucket([]byte("intermediateCA")) + if caBucket == nil { + return fmt.Errorf("intermediateCA does not exist") + } + revokedBucket := caBucket.Bucket(revokedBucketKey) + if revokedBucket == nil { + return fmt.Errorf("intermediateCA revoked bucket does not exist") + } + k, err := sn.GobEncode() + if err != nil { + return fmt.Errorf("failed gob encoding serial number 101: %v", err) + } + if revokedBucket.Get(k) == nil { + return fmt.Errorf("(intermediateCA revoked bucket).Get(101): does not exist") + } + return nil + }); err != nil { + t.Errorf("failed checking revoked entry: %v", err) + } + + revoked, err := b.Revoked("intermediateCA") + if err != nil { + t.Fatalf("Revoked(intermediateCA): got error %v != expected nil", err) + } + if revoked[0].SerialNumber.Cmp(sn) != 0 { + t.Errorf("Revoked(intermediateCA): Revoked serial %v != expected serial %v", revoked[0].SerialNumber, sn) + } + + if _, err = b.Revoked("nonexisting"); err == nil { + t.Error("Revoked(nonexisting): got error nil != expected nonexisting bucket does not exist") + } + revoked, err = b.Revoked("dummyCA") + if err != nil { + t.Errorf("Revoked(dummyCA) with no revoked bucket: got error %v != expected nil", err) + } + if revoked != nil { + t.Errorf("Revoked(dummyCA) with no revoked bucket: got list %v != expected nil", revoked) + } + + if err := db.Update(func(tx *bolt.Tx) error { + caBucket := tx.Bucket([]byte("dummyCA")) + if caBucket == nil { + return fmt.Errorf("missing dummyCA bucket") + } + _, err := caBucket.CreateBucket(revokedBucketKey) + return err + }); err != nil { + t.Errorf("failed creating dummyCA revoked bucket: %v", err) + } + revoked, err = b.Revoked("dummyCA") + if err != nil { + t.Errorf("Revoked(dummyCA) with revoked bucket: got error %v != expected nil", err) + } + if revoked != nil { + t.Errorf("Revoked(dummyCA) with revoked bucket: got list %v != expected nil", revoked) + } +} |
