storj/pkg/encryption/store.go
Jeff Wendling efcdaa43a3
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 17:36:51 +00:00

251 lines
7.6 KiB
Go

// 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
}
// GetDefaultKey returns the default key, or nil if none has been set.
func (s *Store) GetDefaultKey() *storj.Key {
return s.defaultKey
}
// 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)
}
// 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
}