storj/pkg/encryption/path.go
2019-07-05 11:36:35 +03:00

297 lines
8.2 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package encryption
import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
"strings"
"github.com/zeebo/errs"
"storj.io/storj/pkg/paths"
"storj.io/storj/pkg/storj"
)
// EncryptPath encrypts the path using the provided cipher and looking up
// keys from the provided store and bucket.
func EncryptPath(bucket string, path paths.Unencrypted, cipher storj.CipherSuite, store *Store) (
encPath paths.Encrypted, err error) {
// Invalid paths map to invalid paths
if !path.Valid() {
return paths.Encrypted{}, nil
}
if cipher == storj.EncNull {
return paths.NewEncrypted(path.Raw()), nil
}
_, consumed, base := store.LookupUnencrypted(bucket, path)
if base == nil {
return paths.Encrypted{}, errs.New("unable to find encryption base for: %s/%q", bucket, path)
}
remaining, ok := path.Consume(consumed)
if !ok {
return paths.Encrypted{}, errs.New("unable to encrypt bucket path: %s/%q", bucket, path)
}
// if we didn't consume any path, we're at the root of the bucket, and so we have
// to fold the bucket name into the key.
key := &base.Key
if !consumed.Valid() {
key, err = derivePathKeyComponent(key, bucket)
if err != nil {
return paths.Encrypted{}, errs.Wrap(err)
}
}
encrypted, err := EncryptPathRaw(remaining.Raw(), cipher, key)
if err != nil {
return paths.Encrypted{}, errs.Wrap(err)
}
var builder strings.Builder
builder.WriteString(base.Encrypted.Raw())
if len(encrypted) > 0 {
if builder.Len() > 0 {
builder.WriteByte('/')
}
builder.WriteString(encrypted)
}
return paths.NewEncrypted(builder.String()), nil
}
// EncryptPathRaw encrypts the path using the provided key directly. EncryptPath should be
// preferred if possible.
func EncryptPathRaw(raw string, cipher storj.CipherSuite, key *storj.Key) (string, error) {
if cipher == storj.EncNull {
return raw, nil
}
var builder strings.Builder
for iter, i := paths.NewIterator(raw), 0; !iter.Done(); i++ {
component := iter.Next()
encComponent, err := encryptPathComponent(component, cipher, key)
if err != nil {
return "", errs.Wrap(err)
}
key, err = derivePathKeyComponent(key, component)
if err != nil {
return "", errs.Wrap(err)
}
if i > 0 {
builder.WriteByte('/')
}
builder.WriteString(encComponent)
}
return builder.String(), nil
}
// DecryptPath decrypts the path using the provided cipher and looking up
// keys from the provided store and bucket.
func DecryptPath(bucket string, path paths.Encrypted, cipher storj.CipherSuite, store *Store) (
unencPath paths.Unencrypted, err error) {
// Invalid paths map to invalid paths
if !path.Valid() {
return paths.Unencrypted{}, nil
}
if cipher == storj.EncNull {
return paths.NewUnencrypted(path.Raw()), nil
}
_, consumed, base := store.LookupEncrypted(bucket, path)
if base == nil {
return paths.Unencrypted{}, errs.New("unable to find decryption base for: %q", path)
}
remaining, ok := path.Consume(consumed)
if !ok {
return paths.Unencrypted{}, errs.New("unable to decrypt bucket path: %q", path)
}
// if we didn't consume any path, we're at the root of the bucket, and so we have
// to fold the bucket name into the key.
key := &base.Key
if !consumed.Valid() {
key, err = derivePathKeyComponent(key, bucket)
if err != nil {
return paths.Unencrypted{}, errs.Wrap(err)
}
}
decrypted, err := DecryptPathRaw(remaining.Raw(), cipher, key)
if err != nil {
return paths.Unencrypted{}, errs.Wrap(err)
}
var builder strings.Builder
builder.WriteString(base.Unencrypted.Raw())
if len(decrypted) > 0 {
if builder.Len() > 0 {
builder.WriteByte('/')
}
builder.WriteString(decrypted)
}
return paths.NewUnencrypted(builder.String()), nil
}
// DecryptPathRaw decrypts the path using the provided key directly. DecryptPath should be
// preferred if possible.
func DecryptPathRaw(raw string, cipher storj.CipherSuite, key *storj.Key) (string, error) {
if cipher == storj.EncNull {
return raw, nil
}
var builder strings.Builder
for iter, i := paths.NewIterator(raw), 0; !iter.Done(); i++ {
component := iter.Next()
unencComponent, err := decryptPathComponent(component, cipher, key)
if err != nil {
return "", errs.Wrap(err)
}
key, err = derivePathKeyComponent(key, unencComponent)
if err != nil {
return "", errs.Wrap(err)
}
if i > 0 {
builder.WriteByte('/')
}
builder.WriteString(unencComponent)
}
return builder.String(), nil
}
// DeriveContentKey returns the content key for the passed in path by looking up
// the appropriate base key from the store and bucket and deriving the rest.
func DeriveContentKey(bucket string, path paths.Unencrypted, store *Store) (key *storj.Key, err error) {
key, err = DerivePathKey(bucket, path, store)
if err != nil {
return nil, errs.Wrap(err)
}
key, err = DeriveKey(key, "content")
return key, errs.Wrap(err)
}
// DerivePathKey returns the path key for the passed in path by looking up the
// appropriate base key from the store and bucket and deriving the rest.
func DerivePathKey(bucket string, path paths.Unencrypted, store *Store) (key *storj.Key, err error) {
_, consumed, base := store.LookupUnencrypted(bucket, path)
if base == nil {
return nil, errs.New("unable to find encryption base for: %s/%q", bucket, path)
}
// If asking for the key at the bucket, do that and return.
if !path.Valid() {
key, err = derivePathKeyComponent(&base.Key, bucket)
if err != nil {
return nil, errs.Wrap(err)
}
return key, nil
}
remaining, ok := path.Consume(consumed)
if !ok {
return nil, errs.New("unable to derive path key for: %s/%q", bucket, path)
}
// if we didn't consume any path, we're at the root of the bucket, and so we have
// to fold the bucket name into the key.
key = &base.Key
if !consumed.Valid() {
key, err = derivePathKeyComponent(key, bucket)
if err != nil {
return nil, errs.Wrap(err)
}
}
for iter := remaining.Iterator(); !iter.Done(); {
key, err = derivePathKeyComponent(key, iter.Next())
if err != nil {
return nil, errs.Wrap(err)
}
}
return key, nil
}
// derivePathKeyComponent derives a new key from the provided one using the component. It
// should be preferred over DeriveKey when adding path components as it performs the
// necessary transformation to the component.
func derivePathKeyComponent(key *storj.Key, component string) (*storj.Key, error) {
return DeriveKey(key, "path:"+component)
}
// encryptPathComponent encrypts a single path component with the provided cipher and key.
func encryptPathComponent(comp string, cipher storj.CipherSuite, key *storj.Key) (string, error) {
// derive the key for the next path component. this is so that
// every encrypted component has a unique nonce.
derivedKey, err := derivePathKeyComponent(key, comp)
if err != nil {
return "", err
}
// use the derived key to derive the nonce
mac := hmac.New(sha512.New, derivedKey[:])
_, err = mac.Write([]byte("nonce"))
if err != nil {
return "", Error.Wrap(err)
}
nonce := new(storj.Nonce)
copy(nonce[:], mac.Sum(nil))
// encrypt the path components with the parent's key and the derived nonce
cipherText, err := Encrypt([]byte(comp), cipher, key, nonce)
if err != nil {
return "", Error.Wrap(err)
}
nonceSize := storj.NonceSize
if cipher == storj.EncAESGCM {
nonceSize = AESGCMNonceSize
}
// keep the nonce together with the cipher text
return base64.RawURLEncoding.EncodeToString(append(nonce[:nonceSize], cipherText...)), nil
}
// decryptPathComponent decrypts a single path component with the provided cipher and key.
func decryptPathComponent(comp string, cipher storj.CipherSuite, key *storj.Key) (string, error) {
if comp == "" {
return "", nil
}
data, err := base64.RawURLEncoding.DecodeString(comp)
if err != nil {
return "", Error.Wrap(err)
}
nonceSize := storj.NonceSize
if cipher == storj.EncAESGCM {
nonceSize = AESGCMNonceSize
}
if len(data) < nonceSize || nonceSize < 0 {
return "", errs.New("component did not contain enough nonce bytes")
}
// extract the nonce from the cipher text
nonce := new(storj.Nonce)
copy(nonce[:], data[:nonceSize])
decrypted, err := Decrypt(data[nonceSize:], cipher, key, nonce)
if err != nil {
return "", Error.Wrap(err)
}
return string(decrypted), nil
}