From 94eeb58b455cee36b2f8b9e46931fafb9e70c66f Mon Sep 17 00:00:00 2001 From: Jeff Wendling Date: Wed, 10 Jul 2019 12:01:56 -0400 Subject: [PATCH] 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 --- mobile/apikey.go | 103 ++++++++++++++++++ mobile/encryption.go | 15 +++ .../libuplink/LibuplinkInstrumentedTest.java | 47 ++++++++ 3 files changed, 165 insertions(+) create mode 100644 mobile/apikey.go diff --git a/mobile/apikey.go b/mobile/apikey.go new file mode 100644 index 000000000..e18c1ae32 --- /dev/null +++ b/mobile/apikey.go @@ -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 = ¬After + } + if caveat.NotBefore != 0 { + notBefore := time.Unix(caveat.NotBefore, 0) + libCaveat.NotBefore = ¬Before + } + + k, err := a.lib.Restrict(libCaveat) + if err != nil { + return nil, safeError(err) + } + return &APIKey{lib: &k}, nil +} diff --git a/mobile/encryption.go b/mobile/encryption.go index 48d68843c..3edd4ad52 100644 --- a/mobile/encryption.go +++ b/mobile/encryption.go @@ -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 { diff --git a/mobile/libuplink_android/app/src/androidTest/java/io/storj/mobile/libuplink/LibuplinkInstrumentedTest.java b/mobile/libuplink_android/app/src/androidTest/java/io/storj/mobile/libuplink/LibuplinkInstrumentedTest.java index e34e274f4..5d4ff7506 100644 --- a/mobile/libuplink_android/app/src/androidTest/java/io/storj/mobile/libuplink/LibuplinkInstrumentedTest.java +++ b/mobile/libuplink_android/app/src/androidTest/java/io/storj/mobile/libuplink/LibuplinkInstrumentedTest.java @@ -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()); + } + + }