storj/pkg/encryption/store.go

251 lines
7.6 KiB
Go
Raw Normal View History

Create and use an encryption.Store (#2293) * add path implementation This commit adds a pkg/paths package which contains two types, Encrypted and Unencrypted, to statically enforce what is contained in a path. It's part of a refactoring of the code base to be more clear about what is contained in a storj.Path at all the layers. Change-Id: Ifc4d4932da26a97ea99749b8356b4543496a8864 * add encryption store This change adds an encryption.Store type to keep a collection of root keys for arbitrary locations in some buckets. It allows one to look up all of the necessary information to encrypt paths, decrypt paths and decrypt list operations. It adds some exported functions to perform encryption on paths using a Store. Change-Id: I1a3d230c521d65f0ede727f93e1cb389f8be9497 * add shim around streams store This commit changes no functionality, but just reorganizes the code so that changes can be made directly to the streams store implementation without affecting callers. It also adds a Path type that will be used at the interface boundary for the streams store so that it can be sure that it's getting well formed paths that it expects. Change-Id: I50bd682995b185beb653b00562fab62ef11f1ab5 * refactor streams to use encryption store This commit changes the streams store to use the path type as well as the encryption store to handle all of it's encryption and decryption. Some changes were made to how the default key is returned in the encryption store to have it include the case when the bucket exists but no paths matched. The path iterator could also be simplified to not report if a consume was valid: that information is no longer necessary. The kvmetainfo tests were changed to appropriately pass the subtests *testing.T rather than having the closure it executes use the parent one. The test framework now correctly reports which test did the failing. There are still some latent issues with listing in that listing for "a/" and listing for "a" are not the same operation, but we treat them as such. I suspect that there are also issues with paths like "/" or "//foo", but that's for another time. Change-Id: I81cad4ba2850c3d14ba7e632777c4cac93db9472 * use an encryption store at the upper layers Change-Id: Id9b4dd5f27b3ecac863de586e9ae076f4f927f6f * fix linting failures Change-Id: Ifb8378879ad308d4d047a0483850156371a41280 * fix linting in encryption test Change-Id: Ia35647dfe18b0f20fe13763b28e53294f75c38fa * get rid of kvmetainfo rootKey Change-Id: Id795ca03d9417e3fe9634365a121430eb678d6d5 * Fix linting failure for return with else Change-Id: I0b9ffd92be42ffcd8fef7ea735c5fc114a55d3b5 * fix some bugs adding enc store to kvmetainfo Change-Id: I8e765970ba817289c65ec62971ae3bfa2c53a1ba * respond to review feedback Change-Id: I43e2ce29ce2fb6677b1cd6b9469838d80ec92c86
2019-06-24 20:23:07 +01:00
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package encryption
import (
"github.com/zeebo/errs"
"storj.io/storj/pkg/paths"
"storj.io/storj/pkg/storj"
)
// The Store allows one to find the matching most encrypted key and path for
// some unencrypted path. It also reports a mapping of encrypted to unencrypted paths
// at the searched for unencrypted path.
//
// For example, if the Store contains the mappings
//
// b1, u1/u2/u3 => <e1/e2/e3, k3>
// b1, u1/u2/u3/u4 => <e1/e2/e3/e4, k4>
// b1, u1/u5 => <e1/e5, k5>
// b1, u6 => <e6, k6>
// b1, u6/u7/u8 => <e6/e7/e8, k8>
// b2, u1 => <e1', k1'>
//
// Then the following lookups have outputs
//
// b1, u1 => <{e2:u2, e5:u5}, u1, nil>
// b1, u1/u2/u3 => <{e4:u4}, u1/u2/u3, <u1/u2/u3, e1/e2/e3, k3>>
// b1, u1/u2/u3/u6 => <{}, u1/u2/u3/, <u1/u2/u3, e1/e2/e3, k3>>
// b1, u1/u2/u3/u4 => <{}, u1/u2/u3/u4, <u1/u2/u3/u4, e1/e2/e3/e4, k4>>
// b1, u6/u7 => <{e8:u8}, u6/, <u6, e6, k6>>
// b2, u1 => <{}, u1, <u1, e1', k1'>>
type Store struct {
roots map[string]*node
defaultKey *storj.Key
}
// node is a node in the Store graph. It may contain an encryption key and encrypted path,
// a list of children nodes, and data to ensure a bijection between encrypted and unencrypted
// path entries.
type node struct {
unenc map[string]*node // unenc => node
unencMap map[string]string // unenc => enc
enc map[string]*node // enc => node
encMap map[string]string // enc => unenc
base *Base
}
// Base represents a key with which to derive further keys at some encrypted/unencrypted path.
type Base struct {
Unencrypted paths.Unencrypted
Encrypted paths.Encrypted
Key storj.Key
}
// clone returns a copy of the Base. The implementation can be simple because the
// types of its fields do not contain any references.
func (b *Base) clone() *Base {
if b == nil {
return nil
}
bc := *b
return &bc
}
// NewStore constructs a Store.
func NewStore() *Store {
return &Store{roots: make(map[string]*node)}
}
// newNode constructs a node.
func newNode() *node {
return &node{
unenc: make(map[string]*node),
unencMap: make(map[string]string),
enc: make(map[string]*node),
encMap: make(map[string]string),
}
}
// SetDefaultKey adds a default key to be returned for any lookup that does not match a bucket.
func (s *Store) SetDefaultKey(defaultKey *storj.Key) {
s.defaultKey = defaultKey
}
lib/uplink: encryption context (#2349) * lib/uplink: encryption context Change-Id: I5c23dca3286a46b713b30c4997e9ae6e630b2280 * lib/uplink: bucket operation examples Change-Id: Ia0f6e69f365dcff0cf11c731f51b30842bce053b * lib/uplink: encryption key sharing test cases Change-Id: I3a172d565f33f4e591402cdcb9460664a7cc7fbe * fix encrypted path prefix restriction issue Change-Id: I8f3921f9d52aaf4b84039de608b8cbbc88769554 * implement panics in libuplink encryption code todo on cipher suite selection as well as an api concern Change-Id: Ifa39eb3cc4b3443f7d96f9304df9b2ac4ec4085d * implement GetProjectInfo api call to get salt Change-Id: Ic5f6b3be9ea35df48c1aa214ab5d355fb328e2cf * some fixes and accessors for encryption store Change-Id: I3bb61f6712a037900e2a96e72ad4029ec1d3f718 * general fixes to builds/tests/etc Change-Id: I9930fa96acb3b221d9a001f8e274af5729cc8a47 * java bindings changes Change-Id: Ia2bd4c9c69739c8d3154d79616cff1f36fb403b6 * get libuplink examples passing Change-Id: I828f09a144160e0a5dd932324f78491ae2ec8a07 * fix proto.lock file Change-Id: I2fbbf4d0976a7d0473c2645e6dcb21aaa3be7651 * fix proto.lock again Change-Id: I92702cf49e1a340eef6379c2be4f7c4a268112a9 * fix golint issues Change-Id: I631ff9f43307a58e3b25a58cbb4a4cc2495f5eb6 * more linting fixes Change-Id: I51f8f30b367b5bca14c94b15417b9a4c9e7aa0ce * bug fixed by structs bump Change-Id: Ibb03c691fce7606c35c08721b3ef0781ab48a38a * retrigger Change-Id: Ieee0470b6a2d07168a1578552e8e7f271ae93a13 * retrigger Change-Id: I753d63853171e6a436c104ce176048892eb974c5 * semantic merge conflict Change-Id: I9419448496de90340569047a6a16a1b858a7978a * update total to match prod defaults Change-Id: I693d55c1ebb28b5803ee1d26e9e198decf82308b * retrigger Change-Id: I28b74d5d6202f61aa3866fe407d423f6a0a14b9e * retrigger Change-Id: I6fd054885c715f602e2cef623fd464c42e88742c * retrigger Change-Id: I6a01bae88c72406d4ed5a8f13bf8a2b3c650bd2d
2019-06-27 18:36:51 +01:00
// GetDefaultKey returns the default key, or nil if none has been set.
func (s *Store) GetDefaultKey() *storj.Key {
return s.defaultKey
}
Create and use an encryption.Store (#2293) * add path implementation This commit adds a pkg/paths package which contains two types, Encrypted and Unencrypted, to statically enforce what is contained in a path. It's part of a refactoring of the code base to be more clear about what is contained in a storj.Path at all the layers. Change-Id: Ifc4d4932da26a97ea99749b8356b4543496a8864 * add encryption store This change adds an encryption.Store type to keep a collection of root keys for arbitrary locations in some buckets. It allows one to look up all of the necessary information to encrypt paths, decrypt paths and decrypt list operations. It adds some exported functions to perform encryption on paths using a Store. Change-Id: I1a3d230c521d65f0ede727f93e1cb389f8be9497 * add shim around streams store This commit changes no functionality, but just reorganizes the code so that changes can be made directly to the streams store implementation without affecting callers. It also adds a Path type that will be used at the interface boundary for the streams store so that it can be sure that it's getting well formed paths that it expects. Change-Id: I50bd682995b185beb653b00562fab62ef11f1ab5 * refactor streams to use encryption store This commit changes the streams store to use the path type as well as the encryption store to handle all of it's encryption and decryption. Some changes were made to how the default key is returned in the encryption store to have it include the case when the bucket exists but no paths matched. The path iterator could also be simplified to not report if a consume was valid: that information is no longer necessary. The kvmetainfo tests were changed to appropriately pass the subtests *testing.T rather than having the closure it executes use the parent one. The test framework now correctly reports which test did the failing. There are still some latent issues with listing in that listing for "a/" and listing for "a" are not the same operation, but we treat them as such. I suspect that there are also issues with paths like "/" or "//foo", but that's for another time. Change-Id: I81cad4ba2850c3d14ba7e632777c4cac93db9472 * use an encryption store at the upper layers Change-Id: Id9b4dd5f27b3ecac863de586e9ae076f4f927f6f * fix linting failures Change-Id: Ifb8378879ad308d4d047a0483850156371a41280 * fix linting in encryption test Change-Id: Ia35647dfe18b0f20fe13763b28e53294f75c38fa * get rid of kvmetainfo rootKey Change-Id: Id795ca03d9417e3fe9634365a121430eb678d6d5 * Fix linting failure for return with else Change-Id: I0b9ffd92be42ffcd8fef7ea735c5fc114a55d3b5 * fix some bugs adding enc store to kvmetainfo Change-Id: I8e765970ba817289c65ec62971ae3bfa2c53a1ba * respond to review feedback Change-Id: I43e2ce29ce2fb6677b1cd6b9469838d80ec92c86
2019-06-24 20:23:07 +01:00
// Add creates a mapping from the unencrypted path to the encrypted path and key.
func (s *Store) Add(bucket string, unenc paths.Unencrypted, enc paths.Encrypted, key storj.Key) error {
root, ok := s.roots[bucket]
if !ok {
root = newNode()
}
// Perform the addition starting at the root node.
if err := root.add(unenc.Iterator(), enc.Iterator(), &Base{
Unencrypted: unenc,
Encrypted: enc,
Key: key,
}); err != nil {
return err
}
// only update the root for the bucket if the add was successful.
s.roots[bucket] = root
return nil
}
// add places the paths and base into the node tree structure.
func (n *node) add(unenc, enc paths.Iterator, base *Base) error {
if unenc.Done() != enc.Done() {
return errs.New("encrypted and unencrypted paths had different number of components")
}
// If we're done walking the paths, this node must have the provided base.
if unenc.Done() {
n.base = base
return nil
}
// Walk to the next parts and ensure they're consistent with previous additions.
unencPart, encPart := unenc.Next(), enc.Next()
if exUnencPart, ok := n.encMap[encPart]; ok && exUnencPart != unencPart {
return errs.New("conflicting encrypted parts for unencrypted path")
}
if exEncPart, ok := n.unencMap[unencPart]; ok && exEncPart != encPart {
return errs.New("conflicting encrypted parts for unencrypted path")
}
// Look up the child node. Since we're sure the unenc and enc mappings are
// consistent, we can look it up in one map and unconditionally insert it
// into both maps if necessary.
child, ok := n.unenc[unencPart]
if !ok {
child = newNode()
}
// Recurse to the next node in the tree.
if err := child.add(unenc, enc, base); err != nil {
return err
}
// Only add to the maps if the child add was successful.
n.unencMap[unencPart] = encPart
n.encMap[encPart] = unencPart
n.unenc[unencPart] = child
n.enc[encPart] = child
return nil
}
// LookupUnencrypted finds the matching most unencrypted path added to the Store, reports how
// much of the path matched, any known unencrypted paths at the requested path, and if a key
// and encrypted path exists for some prefix of the unencrypted path.
func (s *Store) LookupUnencrypted(bucket string, path paths.Unencrypted) (
revealed map[string]string, consumed paths.Unencrypted, base *Base) {
root, ok := s.roots[bucket]
if ok {
var rawConsumed string
revealed, rawConsumed, base = root.lookup(path.Iterator(), "", nil, true)
consumed = paths.NewUnencrypted(rawConsumed)
}
if base == nil && s.defaultKey != nil {
return nil, paths.Unencrypted{}, &Base{Key: *s.defaultKey}
}
return revealed, consumed, base.clone()
}
// LookupEncrypted finds the matching most encrypted path added to the Store, reports how
// much of the path matched, any known encrypted paths at the requested path, and if a key
// an encrypted path exists for some prefix of the encrypted path.
func (s *Store) LookupEncrypted(bucket string, path paths.Encrypted) (
revealed map[string]string, consumed paths.Encrypted, base *Base) {
root, ok := s.roots[bucket]
if ok {
var rawConsumed string
revealed, rawConsumed, base = root.lookup(path.Iterator(), "", nil, false)
consumed = paths.NewEncrypted(rawConsumed)
}
if base == nil && s.defaultKey != nil {
return nil, paths.Encrypted{}, &Base{Key: *s.defaultKey}
}
return revealed, consumed, base.clone()
}
// lookup searches for the path in the node tree structure.
func (n *node) lookup(path paths.Iterator, bestConsumed string, bestBase *Base, unenc bool) (
map[string]string, string, *Base) {
// Keep track of the best match so far.
if n.base != nil || bestBase == nil {
bestBase, bestConsumed = n.base, path.Consumed()
}
// Pick the tree we're walking down based on the unenc bool.
revealed, children := n.unencMap, n.enc
if unenc {
revealed, children = n.encMap, n.unenc
}
// If we're done walking the path, then return our best match along with the
// revealed paths at this node.
if path.Done() {
return revealed, bestConsumed, bestBase
}
// Walk to the next node in the tree. If there is no node, then report our best match.
child, ok := children[path.Next()]
if !ok {
return nil, bestConsumed, bestBase
}
// Recurse to the next node in the tree.
return child.lookup(path, bestConsumed, bestBase, unenc)
}
lib/uplink: encryption context (#2349) * lib/uplink: encryption context Change-Id: I5c23dca3286a46b713b30c4997e9ae6e630b2280 * lib/uplink: bucket operation examples Change-Id: Ia0f6e69f365dcff0cf11c731f51b30842bce053b * lib/uplink: encryption key sharing test cases Change-Id: I3a172d565f33f4e591402cdcb9460664a7cc7fbe * fix encrypted path prefix restriction issue Change-Id: I8f3921f9d52aaf4b84039de608b8cbbc88769554 * implement panics in libuplink encryption code todo on cipher suite selection as well as an api concern Change-Id: Ifa39eb3cc4b3443f7d96f9304df9b2ac4ec4085d * implement GetProjectInfo api call to get salt Change-Id: Ic5f6b3be9ea35df48c1aa214ab5d355fb328e2cf * some fixes and accessors for encryption store Change-Id: I3bb61f6712a037900e2a96e72ad4029ec1d3f718 * general fixes to builds/tests/etc Change-Id: I9930fa96acb3b221d9a001f8e274af5729cc8a47 * java bindings changes Change-Id: Ia2bd4c9c69739c8d3154d79616cff1f36fb403b6 * get libuplink examples passing Change-Id: I828f09a144160e0a5dd932324f78491ae2ec8a07 * fix proto.lock file Change-Id: I2fbbf4d0976a7d0473c2645e6dcb21aaa3be7651 * fix proto.lock again Change-Id: I92702cf49e1a340eef6379c2be4f7c4a268112a9 * fix golint issues Change-Id: I631ff9f43307a58e3b25a58cbb4a4cc2495f5eb6 * more linting fixes Change-Id: I51f8f30b367b5bca14c94b15417b9a4c9e7aa0ce * bug fixed by structs bump Change-Id: Ibb03c691fce7606c35c08721b3ef0781ab48a38a * retrigger Change-Id: Ieee0470b6a2d07168a1578552e8e7f271ae93a13 * retrigger Change-Id: I753d63853171e6a436c104ce176048892eb974c5 * semantic merge conflict Change-Id: I9419448496de90340569047a6a16a1b858a7978a * update total to match prod defaults Change-Id: I693d55c1ebb28b5803ee1d26e9e198decf82308b * retrigger Change-Id: I28b74d5d6202f61aa3866fe407d423f6a0a14b9e * retrigger Change-Id: I6fd054885c715f602e2cef623fd464c42e88742c * retrigger Change-Id: I6a01bae88c72406d4ed5a8f13bf8a2b3c650bd2d
2019-06-27 18:36:51 +01:00
// Iterate executes the callback with every value that has been Added to the Store.
func (s *Store) Iterate(fn func(string, paths.Unencrypted, paths.Encrypted, storj.Key) error) error {
for bucket, root := range s.roots {
if err := root.iterate(fn, bucket); err != nil {
return err
}
}
return nil
}
// iterate calls the callback if the node has a base, and recurses to its children.
func (n *node) iterate(fn func(string, paths.Unencrypted, paths.Encrypted, storj.Key) error, bucket string) error {
if n.base != nil {
err := fn(bucket, n.base.Unencrypted, n.base.Encrypted, n.base.Key)
if err != nil {
return err
}
}
// recurse down only the unenc map, as the enc map should be the same.
for _, child := range n.unenc {
err := child.iterate(fn, bucket)
if err != nil {
return err
}
}
return nil
}