storj/pkg/paths/paths.go

210 lines
5.1 KiB
Go
Raw Normal View History

// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package paths
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"path"
"strings"
)
// Path is a unique identifier for an object stored in the Storj network
type Path []string
// New creates new Path from the given path segments
func New(segs ...string) Path {
s := path.Join(segs...)
s = strings.Trim(s, "/")
p := strings.Split(s, "/")
if len(p) == 1 && p[0] == "" {
// Avoid building a path with a single empty segment
return []string{}
}
return p
}
// String returns the string representation of the path
func (p Path) String() string {
return path.Join([]string(p)...)
}
Mutex/nsclient- WIP (#104) * working on put request for nsclient * working on put request for nsclient * netstate put * netstate put * wip testing client * wip - testing client and working through some errors * wip - testing client and working through some errors * put request works * put request works for client * get request working * get request working * get request working-minor edit * get request working-minor edit * list request works * list request works * working through delete error * working through delete error * fixed exp client, still working through delete error * fixed exp client, still working through delete error * delete works; fixed formatting issues * delete works; fixed formatting issues * deleted comment * deleted comment * resolving merge conflicts * resolving merge conflict * fixing merge conflict * implemented and modified kayloyans paths file * working on testing * added test for path_test.go * fixed string, read through netstate test * deleted env variables * initial commit for mocking out grpc client- got it working * mocked grpc client * mock put passed test * 2 tests pass for PUT with mock * put requests test pass, wip- want mini review * get tests pass mock * list test working * initial commit for list test * all list req. working, starting on delete tests * delete tests passed * cleaned up tests * resolved merge conflicts * resolved merge conflicts * fixed linter errors * fixed error found in travis * initial commit for fixes from PR comments * fixed pr comments and linting * added error handling for api creds, and rebased * fixes from dennis comments * fixed pr with dennis suggestioon * added copyrights to files * fixed casing per dennis great comment * fixed travis complaint on sprintf
2018-07-19 23:57:22 +01:00
// Bytes serializes the current path to []byte
func (p Path) Bytes() []byte {
return []byte(p.String())
}
// Prepend creates new Path from the current path with the given segments prepended
func (p Path) Prepend(segs ...string) Path {
return New(append(segs, []string(p)...)...)
}
// Append creates new Path from the current path with the given segments appended
func (p Path) Append(segs ...string) Path {
return New(append(p, segs...)...)
}
// HasPrefix tests whether the current path begins with prefix.
func (p Path) HasPrefix(prefix Path) bool {
if len(prefix) > len(p) {
return false
}
for i := 0; i < len(prefix); i++ {
if p[i] != prefix[i] {
return false
}
}
return true
}
// Encrypt creates new Path by encrypting the current path with the given key
func (p Path) Encrypt(key []byte) (encrypted Path, err error) {
encrypted = make([]string, len(p))
for i, seg := range p {
encrypted[i], err = encrypt(seg, key)
if err != nil {
return nil, err
}
key, err = deriveSecret(key, seg)
if err != nil {
return nil, err
}
}
return encrypted, nil
}
// Decrypt creates new Path by decrypting the current path with the given key
func (p Path) Decrypt(key []byte) (decrypted Path, err error) {
decrypted = make([]string, len(p))
for i, seg := range p {
decrypted[i], err = decrypt(seg, key)
if err != nil {
return nil, err
}
key, err = deriveSecret(key, decrypted[i])
if err != nil {
return nil, err
}
}
return decrypted, nil
}
// DeriveKey derives the key for the given depth from the given root key
// This method must be called on an unencrypted path.
func (p Path) DeriveKey(key []byte, depth int) (derivedKey []byte, err error) {
if depth < 0 {
return nil, Error.New("negative depth")
}
if depth > len(p) {
return nil, Error.New("depth greater than path length")
}
derivedKey = key
for i := 0; i < depth; i++ {
derivedKey, err = deriveSecret(derivedKey, p[i])
if err != nil {
return nil, err
}
}
return derivedKey, nil
}
Stream encryption (#302) * begin adding encryption for remote pieces * begin adding decryption * add encryption key as arg to Put and Get * move encryption/decryption to object store * Add encryption key to object store constructor * Add the erasure scheme to object store constructor * Ensure decrypter is initialized with the stripe size used by encrypter * Revert "Ensure decrypter is initialized with the stripe size used by encrypter" This reverts commit 07272333f461606edfb43ad106cc152f37a3bd46. * Revert "Add the erasure scheme to object store constructor" This reverts commit ea5e793b536159d993b96e3db69a37c1656a193c. * move encryption to stream store * move decryption stuff to stream store * revert changes in object store * add encryptedBlockSize and close rangers on error during Get * calculate padding sizes correctly * encryptedBlockSize -> encryptionBlockSize * pass encryption key and block size into stream store * remove encryption key and block size from object store constructor * move encrypter/decrypter initialization * remove unnecessary cast * Fix padding issue * Fix linter * add todos * use random encryption key for data encryption. Store an encrypted copy of this key in segment metadata * use different encryption key for each segment * encrypt data in one step if it is small enough * refactor and move encryption stuff * fix errors related to nil slices passed to copy * fix encrypter vs. decrypter bug * put encryption stuff in eestream * get captplanet test to pass * fix linting errors * add types for encryption keys/nonces and clean up * fix tests * more review changes * add Cipher type for encryption stuff * fix rs_test * Simplify type casting of key and nonce * Init starting nonce to the segment index * don't copy derived key * remove default encryption key; force user to explicitly set it * move getSegmentPath to streams package * dont require user to specify encryption key for captplanet * rename GenericKey and GenericNonce to Key and Nonce * review changes * fix linting error * Download uses the encryption type from metadata * Store enc block size in metadata and use it for download
2018-09-26 14:32:23 +01:00
// DeriveContentKey derives the key for the encrypted object data using the root key
// This method must be called on an unencrypted path.
func (p Path) DeriveContentKey(key []byte) (derivedKey *[32]byte, err error) {
if len(p) == 0 {
return nil, Error.New("path is empty")
}
d, err := p.DeriveKey(key, len(p)-1)
if err != nil {
return nil, err
}
d, err = deriveSecret(d, "content")
if err != nil {
return nil, err
}
derivedKey = new([32]byte)
copy((*derivedKey)[:], d[:32])
return derivedKey, nil
}
func encrypt(text string, secret []byte) (cipherText string, err error) {
key, nonce, err := getAESGCMKeyAndNonce(secret)
if err != nil {
return "", Error.Wrap(err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", Error.Wrap(err)
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", Error.Wrap(err)
}
data := aesgcm.Seal(nil, nonce, []byte(text), nil)
cipherText = base64.RawURLEncoding.EncodeToString(data)
// prepend version number to the cipher text
return "1" + cipherText, nil
}
func decrypt(cipherText string, secret []byte) (text string, err error) {
if cipherText == "" {
return "", Error.New("empty cipher text")
}
// check the version number, only "1" is supported for now
if cipherText[0] != '1' {
return "", Error.New("invalid version number")
}
data, err := base64.RawURLEncoding.DecodeString(cipherText[1:])
if err != nil {
return "", Error.Wrap(err)
}
key, nonce, err := getAESGCMKeyAndNonce(secret)
if err != nil {
return "", Error.Wrap(err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", Error.Wrap(err)
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", Error.Wrap(err)
}
decrypted, err := aesgcm.Open(nil, nonce, data, nil)
if err != nil {
return "", Error.Wrap(err)
}
return string(decrypted), nil
}
func getAESGCMKeyAndNonce(secret []byte) (key, nonce []byte, err error) {
mac := hmac.New(sha512.New, secret)
_, err = mac.Write([]byte("enc"))
if err != nil {
return nil, nil, Error.Wrap(err)
}
key = mac.Sum(nil)[:32]
mac.Reset()
_, err = mac.Write([]byte("nonce"))
if err != nil {
return nil, nil, Error.Wrap(err)
}
nonce = mac.Sum(nil)[:12]
return key, nonce, nil
}
func deriveSecret(secret []byte, child string) (derived []byte, err error) {
mac := hmac.New(sha512.New, secret)
_, err = mac.Write([]byte(child))
if err != nil {
return nil, Error.Wrap(err)
}
return mac.Sum(nil), nil
}