aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Clerc <jclerc@google.com>2017-02-14 15:28:58 +0100
committerJeremy Clerc <jclerc@google.com>2017-02-14 15:28:58 +0100
commitaee8c433051c70bd42d6550a38a11c2830b1b223 (patch)
treeaa384b0e6823793b72edde767eed7ab457dcf2e0
parentccb8768272387252f897e3b216afd16e34d46032 (diff)
downloadeasypki-aee8c433051c70bd42d6550a38a11c2830b1b223.tar.xz
Add bolt store backend.
-rw-r--r--pkg/easypki/easypki_test.go30
-rw-r--r--pkg/store/bolt.go184
-rw-r--r--pkg/store/bolt_test.go212
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)
+ }
+}