efcdaa43a3
* 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
188 lines
5.4 KiB
Go
188 lines
5.4 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package uplink
|
|
|
|
import (
|
|
"github.com/btcsuite/btcutil/base58"
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/storj/pkg/encryption"
|
|
"storj.io/storj/pkg/macaroon"
|
|
"storj.io/storj/pkg/paths"
|
|
"storj.io/storj/pkg/pb"
|
|
"storj.io/storj/pkg/storj"
|
|
)
|
|
|
|
const (
|
|
defaultCipher = storj.EncAESGCM
|
|
)
|
|
|
|
// EncryptionCtx represents an encryption context. It holds information about
|
|
// how various buckets and objects should be encrypted and decrypted.
|
|
type EncryptionCtx struct {
|
|
store *encryption.Store
|
|
}
|
|
|
|
// NewEncryptionCtx creates an encryption ctx
|
|
func NewEncryptionCtx() *EncryptionCtx {
|
|
return &EncryptionCtx{
|
|
store: encryption.NewStore(),
|
|
}
|
|
}
|
|
|
|
// NewEncryptionCtxWithDefaultKey creates an encryption ctx with a default key set
|
|
func NewEncryptionCtxWithDefaultKey(defaultKey storj.Key) *EncryptionCtx {
|
|
ec := NewEncryptionCtx()
|
|
ec.SetDefaultKey(defaultKey)
|
|
return ec
|
|
}
|
|
|
|
// Store returns the underlying encryption store for the context.
|
|
func (s *EncryptionCtx) Store() *encryption.Store {
|
|
return s.store
|
|
}
|
|
|
|
// SetDefaultKey sets the default key for the encryption context.
|
|
func (s *EncryptionCtx) SetDefaultKey(defaultKey storj.Key) {
|
|
s.store.SetDefaultKey(&defaultKey)
|
|
}
|
|
|
|
// Import merges the other encryption context into this one. In cases
|
|
// of conflicting path decryption settings (including if both contexts have
|
|
// a default key), the new settings are kept.
|
|
func (s *EncryptionCtx) Import(other *EncryptionCtx) error {
|
|
if key := other.store.GetDefaultKey(); key != nil {
|
|
s.store.SetDefaultKey(key)
|
|
}
|
|
return other.store.Iterate(s.store.Add)
|
|
}
|
|
|
|
// EncryptionRestriction represents a scenario where some set of objects
|
|
// may need to be encrypted/decrypted
|
|
type EncryptionRestriction struct {
|
|
Bucket string
|
|
PathPrefix storj.Path
|
|
}
|
|
|
|
// Restrict creates a new EncryptionCtx with no default key, where the key material
|
|
// in the new context is just enough to allow someone to access all of the given
|
|
// restrictions but no more.
|
|
func (s *EncryptionCtx) Restrict(apiKey APIKey, restrictions ...EncryptionRestriction) (APIKey, *EncryptionCtx, error) {
|
|
if len(restrictions) == 0 {
|
|
// Should the function signature be
|
|
// func (s *EncryptionCtx) Restrict(apiKey APIKey, restriction EncryptionRestriction, restrictions ...EncryptionRestriction) (APIKey, *EncryptionCtx, error) {
|
|
// so we don't have to do this test?
|
|
return APIKey{}, nil, errs.New("at least one restriction required")
|
|
}
|
|
|
|
caveat := macaroon.Caveat{}
|
|
encCtx := NewEncryptionCtx()
|
|
|
|
for _, res := range restrictions {
|
|
unencPath := paths.NewUnencrypted(res.PathPrefix)
|
|
cipher := storj.AESGCM // TODO(jeff): pick the right path cipher
|
|
|
|
encPath, err := encryption.StoreEncryptPath(res.Bucket, unencPath, cipher, s.store)
|
|
if err != nil {
|
|
return APIKey{}, nil, err
|
|
}
|
|
derivedKey, err := encryption.StoreDerivePathKey(res.Bucket, unencPath, s.store)
|
|
if err != nil {
|
|
return APIKey{}, nil, err
|
|
}
|
|
|
|
if err := encCtx.store.Add(res.Bucket, unencPath, encPath, *derivedKey); err != nil {
|
|
return APIKey{}, nil, err
|
|
}
|
|
caveat.AllowedPaths = append(caveat.AllowedPaths, &macaroon.Caveat_Path{
|
|
Bucket: []byte(res.Bucket),
|
|
EncryptedPathPrefix: []byte(encPath.Raw()),
|
|
})
|
|
}
|
|
|
|
apiKey, err := apiKey.Restrict(caveat)
|
|
if err != nil {
|
|
return APIKey{}, nil, err
|
|
}
|
|
|
|
return apiKey, encCtx, nil
|
|
}
|
|
|
|
// Serialize turns an EncryptionCtx into base58
|
|
func (s *EncryptionCtx) Serialize() (string, error) {
|
|
var storeEntries []*pb.EncryptionCtx_StoreEntry
|
|
err := s.store.Iterate(func(bucket string, unenc paths.Unencrypted, enc paths.Encrypted, key storj.Key) error {
|
|
storeEntries = append(storeEntries, &pb.EncryptionCtx_StoreEntry{
|
|
Bucket: []byte(bucket),
|
|
UnencryptedPath: []byte(unenc.Raw()),
|
|
EncryptedPath: []byte(enc.Raw()),
|
|
Key: key[:],
|
|
})
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var defaultKey []byte
|
|
if key := s.store.GetDefaultKey(); key != nil {
|
|
defaultKey = key[:]
|
|
}
|
|
|
|
data, err := proto.Marshal(&pb.EncryptionCtx{
|
|
DefaultKey: defaultKey,
|
|
StoreEntries: storeEntries,
|
|
})
|
|
if err != nil {
|
|
return "", errs.New("unable to marshal encryption ctx: %v", err)
|
|
}
|
|
|
|
return base58.CheckEncode(data, 0), nil
|
|
|
|
}
|
|
|
|
// ParseEncryptionCtx parses a base58 serialized encryption context into a working one.
|
|
func ParseEncryptionCtx(b58data string) (*EncryptionCtx, error) {
|
|
data, version, err := base58.CheckDecode(b58data)
|
|
if err != nil || version != 0 {
|
|
return nil, errs.New("invalid encryption context format")
|
|
}
|
|
|
|
p := new(pb.EncryptionCtx)
|
|
if err := proto.Unmarshal(data, p); err != nil {
|
|
return nil, errs.New("unable to unmarshal encryption context: %v", err)
|
|
}
|
|
|
|
encCtx := NewEncryptionCtx()
|
|
|
|
if len(p.DefaultKey) > 0 {
|
|
if len(p.DefaultKey) != len(storj.Key{}) {
|
|
return nil, errs.New("invalid default key in encryption context")
|
|
}
|
|
var defaultKey storj.Key
|
|
copy(defaultKey[:], p.DefaultKey)
|
|
encCtx.SetDefaultKey(defaultKey)
|
|
}
|
|
|
|
for _, entry := range p.StoreEntries {
|
|
if len(entry.Key) != len(storj.Key{}) {
|
|
return nil, errs.New("invalid key in encryption context entry")
|
|
}
|
|
var key storj.Key
|
|
copy(key[:], entry.Key)
|
|
|
|
err := encCtx.store.Add(
|
|
string(entry.Bucket),
|
|
paths.NewUnencrypted(string(entry.UnencryptedPath)),
|
|
paths.NewEncrypted(string(entry.EncryptedPath)),
|
|
key)
|
|
if err != nil {
|
|
return nil, errs.New("invalid encryption context entry: %v", err)
|
|
}
|
|
}
|
|
|
|
return encCtx, nil
|
|
}
|