mobile: add a way to get an encryption access at some path root (#2519)

* mobile: add a way to get an encryption access at some path root

this exposes a way to have keys deeper than the bucket root

* APIKey + Caveat exported

* use safeError

* return nil
This commit is contained in:
Jeff Wendling 2019-07-10 12:01:56 -04:00 committed by JT Olio
parent 10547cc1ea
commit 94eeb58b45
3 changed files with 165 additions and 0 deletions

103
mobile/apikey.go Normal file
View File

@ -0,0 +1,103 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package mobile
import (
"time"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/macaroon"
)
// Caveat TODO
type Caveat struct {
DisallowReads bool
DisallowWrites bool
DisallowLists bool
DisallowDeletes bool
AllowedPaths []*CaveatPath
// if set, the validity time window
NotAfter int64
NotBefore int64
// nonce is set to some random bytes so that you can make arbitrarily
// many restricted macaroons with the same (or no) restrictions.
Nonce []byte
}
// CaveatPath If any entries exist, require all access to happen in at least
// one of them.
type CaveatPath struct {
Bucket []byte
EncryptedPathPrefix []byte
}
// NewCaveat TODO
func NewCaveat() *Caveat {
return &Caveat{
AllowedPaths: make([]*CaveatPath, 0),
}
}
// AddCaveatPath TODO
func (caveat Caveat) AddCaveatPath(path *CaveatPath) {
caveat.AllowedPaths = append(caveat.AllowedPaths, path)
}
// APIKey represents an access credential to certain resources
type APIKey struct {
lib *libuplink.APIKey
}
// Serialize serializes the API key to a string
func (a APIKey) Serialize() string {
return a.lib.Serialize()
}
// IsZero returns if the api key is an uninitialized value
func (a *APIKey) IsZero() bool {
return a.IsZero()
}
// ParseAPIKey parses an API key
func ParseAPIKey(val string) (*APIKey, error) {
k, err := libuplink.ParseAPIKey(val)
if err != nil {
return nil, safeError(err)
}
return &APIKey{lib: &k}, nil
}
// Restrict generates a new APIKey with the provided Caveat attached.
func (a APIKey) Restrict(caveat *Caveat) (*APIKey, error) {
paths := make([]*macaroon.Caveat_Path, 0)
for _, path := range caveat.AllowedPaths {
paths = append(paths, &macaroon.Caveat_Path{
Bucket: path.Bucket,
EncryptedPathPrefix: path.EncryptedPathPrefix,
})
}
libCaveat := macaroon.Caveat{
DisallowReads: caveat.DisallowReads,
DisallowWrites: caveat.DisallowWrites,
DisallowLists: caveat.DisallowLists,
DisallowDeletes: caveat.DisallowDeletes,
AllowedPaths: paths,
Nonce: caveat.Nonce,
}
if caveat.NotAfter != 0 {
notAfter := time.Unix(caveat.NotAfter, 0)
libCaveat.NotAfter = &notAfter
}
if caveat.NotBefore != 0 {
notBefore := time.Unix(caveat.NotBefore, 0)
libCaveat.NotBefore = &notBefore
}
k, err := a.lib.Restrict(libCaveat)
if err != nil {
return nil, safeError(err)
}
return &APIKey{lib: &k}, nil
}

View File

@ -5,6 +5,7 @@ package mobile
import (
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/paths"
"storj.io/storj/pkg/storj"
)
@ -18,6 +19,20 @@ func NewEncryptionAccess() *EncryptionAccess {
return &EncryptionAccess{lib: libuplink.NewEncryptionAccess()}
}
// NewEncryptionAccessWithRoot constructs an encryption access with a key rooted at the provided path inside of a bucket.
func NewEncryptionAccessWithRoot(bucket, unencryptedPath, encryptedPath string, keyData []byte) (*EncryptionAccess, error) {
key, err := storj.NewKey(keyData)
if err != nil {
return nil, safeError(err)
}
encAccess := libuplink.NewEncryptionAccess()
err = encAccess.Store().Add(bucket, paths.NewUnencrypted(unencryptedPath), paths.NewEncrypted(encryptedPath), *key)
if err != nil {
return nil, safeError(err)
}
return &EncryptionAccess{lib: encAccess}, nil
}
// SetDefaultKey sets the default key to use when no matching keys are found
// for the encryption context.
func (e *EncryptionAccess) SetDefaultKey(keyData []byte) error {

View File

@ -338,4 +338,51 @@ public class LibuplinkInstrumentedTest {
uplink.close();
}
}
@Test
public void testEncryptionAccessWithRoot() throws Exception {
Config config = new Config();
Uplink uplink = new Uplink(config, filesDir);
try {
Project project = uplink.openProject(VALID_SATELLITE_ADDRESS, VALID_API_KEY);
try {
byte[] saltedKey = project.saltedKeyFromPassphrase("some-passphrase");
EncryptionAccess ea = Mobile.newEncryptionAccessWithRoot("bucket", "unencryptedPath", "encryptedPath", saltedKey);
String serialized = ea.serialize();
assertNotEquals("", serialized);
} finally {
project.close();
}
} finally {
uplink.close();
}
}
@Test
public void testApiKey() throws Exception {
String apiKeyData = "13YqeKQiA3ANSuDu4rqX6eGs3YWox9GRi9rEUKy1HidXiNNm6a5SiE49Hk9gomHZVcQhq4eFQh8yhDgfGKg268j6vqWKEhnJjFPLqAP";
APIKey apiKey = Mobile.parseAPIKey(apiKeyData);
String serialized = apiKey.serialize();
assertEquals(serialized, apiKeyData);
Caveat caveat = new Caveat();
caveat.setDisallowDeletes(true);
caveat.setDisallowWrites(true);
caveat.setDisallowReads(true);
caveat.setDisallowLists(true);
caveat.setNotAfter(100);
caveat.setNotBefore(50);
CaveatPath path = new CaveatPath();
path.setBucket("bucket".getBytes());
path.setEncryptedPathPrefix("123456".getBytes());
caveat.addCaveatPath(path);
APIKey newAPIKey = apiKey.restrict(caveat);
assertNotEquals("", newAPIKey.serialize());
}
}