This is an automated email from the ASF dual-hosted git repository. smihaylov pushed a commit to branch create-identity in repository https://gitbox.apache.org/repos/asf/incubator-milagro-dta.git
commit 7b7a02dda794d91aa8339397b4ef29b014dba384 Author: Stanislav Mihaylov <[email protected]> AuthorDate: Wed Sep 25 11:06:40 2019 +0300 KeyStore interface to keep secrets File KeyStore implementation to keep secrets in a json file --- libs/keystore/filestore.go | 115 ++++++++++++++++++++++++++++++++++++++ libs/keystore/filestore_test.go | 77 +++++++++++++++++++++++++ libs/keystore/keystore.go | 42 ++++++++++++++ libs/keystore/memorystore.go | 64 +++++++++++++++++++++ libs/keystore/memorystore_test.go | 44 +++++++++++++++ 5 files changed, 342 insertions(+) diff --git a/libs/keystore/filestore.go b/libs/keystore/filestore.go new file mode 100644 index 0000000..9c8f2e9 --- /dev/null +++ b/libs/keystore/filestore.go @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 keystore + +import ( + "encoding/json" + "io/ioutil" + "os" + "sync" + + "github.com/pkg/errors" +) + +// FileStore is the key Store implementation storing the keys in a file +type FileStore struct { + sync.RWMutex + filePath string + keys map[string][]byte +} + +// NewFileStore creates a new FileStore +func NewFileStore(filePath string) (Store, error) { + fs := &FileStore{ + filePath: filePath, + keys: map[string][]byte{}, + } + + if err := fs.loadKeys(); err != nil { + return nil, err + } + + return fs, nil +} + +// Set stores multiple keys at once +func (f *FileStore) Set(keys ...Key) error { + for _, key := range keys { + f.keys[key.Name] = make([]byte, len(key.Key)) + copy(f.keys[key.Name], key.Key) + } + + return f.storeKeys() +} + +// Get retrieves multiple keys +func (f *FileStore) Get(names ...string) (map[string][]byte, error) { + keys := map[string][]byte{} + for _, name := range names { + k, ok := f.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + keys[name] = make([]byte, len(k)) + copy(keys[name], k) + } + return keys, nil +} + +// TODO: Lock the file + +func (f *FileStore) loadKeys() error { + f.RLock() + defer f.RUnlock() + rawKeys, err := ioutil.ReadFile(f.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return errors.Wrap(err, "Load keys") + } + + return json.Unmarshal(rawKeys, &(f).keys) +} + +func (f *FileStore) storeKeys() error { + f.Lock() + defer f.Unlock() + rawKeys, err := json.Marshal(f.keys) + if err != nil { + return err + } + + // Get the file permissions + var perm os.FileMode + fi, err := os.Stat(f.filePath) + if err != nil { + if !os.IsNotExist(err) { + return errors.Wrap(err, "Get key file permissions") + } + perm = 0600 + } else { + perm = fi.Mode().Perm() + } + + if err := ioutil.WriteFile(f.filePath, rawKeys, perm); err != nil { + return errors.Wrap(err, "Store keys") + } + + return nil +} diff --git a/libs/keystore/filestore_test.go b/libs/keystore/filestore_test.go new file mode 100644 index 0000000..0bbb189 --- /dev/null +++ b/libs/keystore/filestore_test.go @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 keystore + +import ( + "bytes" + "crypto/rand" + "fmt" + "os" + "path/filepath" + "testing" + "time" +) + +func TestFileStore(t *testing.T) { + keys := []Key{{"key1", []byte{1}}, {"key2", []byte{1, 2}}, {"key3", []byte{1, 2, 3}}} + keynames := []string{"key1", "key2", "key3"} + + fn := tmpFileName() + defer func() { + if err := os.Remove(fn); err != nil { + t.Logf("Warning! Temp file could not be deleted (%v): %v", err, fn) + } + }() + + fs, err := NewFileStore(fn) + if err != nil { + t.Fatal(err) + } + fs.Set(keys...) + skeys, err := fs.Get(keynames...) + if err != nil { + t.Fatal(err) + } + for _, k := range keys { + if !bytes.Equal(k.Key, skeys[k.Name]) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", k.Name, k.Key, skeys[k.Name]) + } + } + + fs1, err := NewFileStore(fn) + if err != nil { + t.Fatal(err) + } + s1keys, err := fs1.Get(keynames...) + if err != nil { + t.Fatal(err) + } + for _, k := range keys { + if !bytes.Equal(k.Key, s1keys[k.Name]) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", k.Name, k.Key, s1keys[k.Name]) + } + } +} + +func tmpFileName() string { + rnd := make([]byte, 8) + rand.Read(rnd) + ts := time.Now().UnixNano() + + return filepath.Join(os.TempDir(), fmt.Sprintf("keystore-%v-%x.tmp", ts, rnd)) +} diff --git a/libs/keystore/keystore.go b/libs/keystore/keystore.go new file mode 100644 index 0000000..8f4da80 --- /dev/null +++ b/libs/keystore/keystore.go @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 keystore - keep secrets +*/ +package keystore + +import "github.com/pkg/errors" + +var ( + // ErrKeyNotFound is returned when a key is not found in the store + ErrKeyNotFound = errors.New("Key not found") +) + +// Store is the keystore interface +type Store interface { + // Set stores multiple keys at once + Set(...Key) error + // Get retrieves multiple keys + Get(...string) (map[string][]byte, error) +} + +// Key represent a single key +type Key struct { + Name string + Key []byte +} diff --git a/libs/keystore/memorystore.go b/libs/keystore/memorystore.go new file mode 100644 index 0000000..58e4470 --- /dev/null +++ b/libs/keystore/memorystore.go @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 keystore + +import ( + "sync" +) + +// MemoryStore is the in-memory implementation of key store +type MemoryStore struct { + sync.RWMutex + keys map[string][]byte +} + +// NewMemoryStore creates a new MemoryStore +func NewMemoryStore() (Store, error) { + return &MemoryStore{ + keys: map[string][]byte{}, + }, nil +} + +// Set stores multiple keys at once +func (f *MemoryStore) Set(keys ...Key) error { + f.Lock() + defer f.Unlock() + + for _, key := range keys { + f.keys[key.Name] = make([]byte, len(key.Key)) + copy(f.keys[key.Name], key.Key) + } + + return nil +} + +// Get retrieves multiple keys +func (f *MemoryStore) Get(names ...string) (map[string][]byte, error) { + f.RLock() + defer f.RUnlock() + keys := map[string][]byte{} + for _, name := range names { + k, ok := f.keys[name] + if !ok { + return nil, ErrKeyNotFound + } + keys[name] = make([]byte, len(k)) + copy(keys[name], k) + } + return keys, nil +} diff --git a/libs/keystore/memorystore_test.go b/libs/keystore/memorystore_test.go new file mode 100644 index 0000000..2577102 --- /dev/null +++ b/libs/keystore/memorystore_test.go @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 keystore + +import ( + "bytes" + "testing" +) + +func TestMemoryStore(t *testing.T) { + keys := []Key{{"key1", []byte{1}}, {"key2", []byte{1, 2}}, {"key3", []byte{1, 2, 3}}} + keynames := []string{"key1", "key2", "key3"} + + fs, err := NewMemoryStore() + if err != nil { + t.Fatal(err) + } + fs.Set(keys...) + skeys, err := fs.Get(keynames...) + if err != nil { + t.Fatal(err) + } + for _, k := range keys { + if !bytes.Equal(k.Key, skeys[k.Name]) { + t.Errorf("Key not match: %v. Expected: %v, Found: %v", k.Name, k.Key, skeys[k.Name]) + } + } + +}
