2018-05-30 16:33:27 +01:00
|
|
|
// Copyright (C) 2018 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package paths
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/base64"
|
2018-07-02 16:21:32 +01:00
|
|
|
"path"
|
|
|
|
"strings"
|
2018-05-30 16:33:27 +01:00
|
|
|
)
|
|
|
|
|
2018-07-02 16:21:32 +01:00
|
|
|
// 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, "/")
|
2018-07-27 07:02:59 +01:00
|
|
|
p := strings.Split(s, "/")
|
|
|
|
if len(p) == 1 && p[0] == "" {
|
|
|
|
// Avoid building a path with a single empty segment
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
return p
|
2018-07-02 16:21:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the string representation of the path
|
|
|
|
func (p Path) String() string {
|
|
|
|
return path.Join([]string(p)...)
|
|
|
|
}
|
|
|
|
|
2018-07-19 23:57:22 +01:00
|
|
|
// Bytes serializes the current path to []byte
|
|
|
|
func (p Path) Bytes() []byte {
|
|
|
|
return []byte(p.String())
|
|
|
|
}
|
|
|
|
|
2018-07-02 16:21:32 +01:00
|
|
|
// 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...)...)
|
|
|
|
}
|
|
|
|
|
2018-08-16 15:32:28 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-07-02 16:21:32 +01:00
|
|
|
// 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)
|
2018-05-30 16:33:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-16 20:22:34 +01:00
|
|
|
key, err = deriveSecret(key, seg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
2018-07-02 16:21:32 +01:00
|
|
|
return encrypted, nil
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
|
|
|
|
2018-07-02 16:21:32 +01:00
|
|
|
// 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)
|
2018-05-30 16:33:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-16 20:22:34 +01:00
|
|
|
key, err = deriveSecret(key, decrypted[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
2018-07-02 16:21:32 +01:00
|
|
|
return decrypted, nil
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
|
|
|
|
2018-07-02 16:21:32 +01:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2018-05-30 16:33:27 +01:00
|
|
|
derivedKey = key
|
2018-07-02 16:21:32 +01:00
|
|
|
for i := 0; i < depth; i++ {
|
2018-07-16 20:22:34 +01:00
|
|
|
derivedKey, err = deriveSecret(derivedKey, p[i])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
2018-07-02 16:21:32 +01:00
|
|
|
return derivedKey, nil
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-05-30 16:33:27 +01:00
|
|
|
func encrypt(text string, secret []byte) (cipherText string, err error) {
|
2018-07-16 20:22:34 +01:00
|
|
|
key, nonce, err := getAESGCMKeyAndNonce(secret)
|
|
|
|
if err != nil {
|
|
|
|
return "", Error.Wrap(err)
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
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)
|
|
|
|
}
|
2018-07-16 20:22:34 +01:00
|
|
|
key, nonce, err := getAESGCMKeyAndNonce(secret)
|
|
|
|
if err != nil {
|
|
|
|
return "", Error.Wrap(err)
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-07-16 20:22:34 +01:00
|
|
|
func getAESGCMKeyAndNonce(secret []byte) (key, nonce []byte, err error) {
|
2018-05-30 16:33:27 +01:00
|
|
|
mac := hmac.New(sha512.New, secret)
|
2018-07-16 20:22:34 +01:00
|
|
|
_, err = mac.Write([]byte("enc"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, Error.Wrap(err)
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
key = mac.Sum(nil)[:32]
|
|
|
|
mac.Reset()
|
2018-07-16 20:22:34 +01:00
|
|
|
_, err = mac.Write([]byte("nonce"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, Error.Wrap(err)
|
|
|
|
}
|
2018-05-30 16:33:27 +01:00
|
|
|
nonce = mac.Sum(nil)[:12]
|
2018-07-16 20:22:34 +01:00
|
|
|
return key, nonce, nil
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|
|
|
|
|
2018-07-16 20:22:34 +01:00
|
|
|
func deriveSecret(secret []byte, child string) (derived []byte, err error) {
|
2018-05-30 16:33:27 +01:00
|
|
|
mac := hmac.New(sha512.New, secret)
|
2018-07-16 20:22:34 +01:00
|
|
|
_, err = mac.Write([]byte(child))
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
return mac.Sum(nil), nil
|
2018-05-30 16:33:27 +01:00
|
|
|
}
|