mirror of
https://github.com/kopia/kopia.git
synced 2026-05-01 19:34:26 -04:00
removed support for per-objectID encryption
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
@@ -81,33 +80,6 @@ func (fi *unencryptedFormat) Decrypt(cipherText []byte, oid ObjectID) ([]byte, e
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
// Since we never share keys, using constant IV is fine.
|
||||
// Instead of using all-zero, we use this one.
|
||||
var constantIV = []byte("kopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopiakopia")
|
||||
|
||||
// convergentEncryptionFormat implements encrypted format where encryption key is derived from the data and HMAC secret and IV is constant.
|
||||
// By sharing HMAC secret alone, this allows multiple parties in posession of the same file to generate identical block IDs and encryption keys.
|
||||
type convergentEncryptionFormat struct {
|
||||
digestFunc digestFunction
|
||||
createCipher func(key []byte) (cipher.Block, error)
|
||||
keyBytes int
|
||||
}
|
||||
|
||||
func (fi *convergentEncryptionFormat) ComputeObjectID(data []byte) ObjectID {
|
||||
h := fi.digestFunc(data)
|
||||
p := len(h) - fi.keyBytes
|
||||
|
||||
return ObjectID{StorageBlock: hex.EncodeToString(h[0:p]), EncryptionKey: h[p:]}
|
||||
}
|
||||
|
||||
func (fi *convergentEncryptionFormat) Encrypt(plainText []byte, oid ObjectID) ([]byte, error) {
|
||||
return symmetricEncrypt(fi.createCipher, oid.EncryptionKey, constantIV, plainText)
|
||||
}
|
||||
|
||||
func (fi *convergentEncryptionFormat) Decrypt(cipherText []byte, oid ObjectID) ([]byte, error) {
|
||||
return symmetricEncrypt(fi.createCipher, oid.EncryptionKey, constantIV, cipherText)
|
||||
}
|
||||
|
||||
// syntheticIVEncryptionFormat implements encrypted format with single master AES key and StorageBlock==IV that's
|
||||
// derived from HMAC-SHA256(content, secret).
|
||||
type syntheticIVEncryptionFormat struct {
|
||||
@@ -176,12 +148,6 @@ func init() {
|
||||
"UNENCRYPTED_HMAC_SHA256_128": func(f *Format) (ObjectFormatter, error) {
|
||||
return &unencryptedFormat{computeHMAC(sha256.New, f.Secret, 16)}, nil
|
||||
},
|
||||
"ENCRYPTED_HMAC_SHA512_384_AES256": func(f *Format) (ObjectFormatter, error) {
|
||||
return &convergentEncryptionFormat{computeHMAC(sha512.New384, f.Secret, sha512.Size384), aes.NewCipher, 32}, nil
|
||||
},
|
||||
"ENCRYPTED_HMAC_SHA512_AES256": func(f *Format) (ObjectFormatter, error) {
|
||||
return &convergentEncryptionFormat{computeHMAC(sha512.New, f.Secret, sha512.Size), aes.NewCipher, 32}, nil
|
||||
},
|
||||
"ENCRYPTED_HMAC_SHA256_AES256_SIV": func(f *Format) (ObjectFormatter, error) {
|
||||
if len(f.MasterKey) < 32 {
|
||||
return nil, fmt.Errorf("master key is not set")
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
@@ -33,16 +32,11 @@
|
||||
// "D295754edeb35c17911b1fdf853f572fe" // storage block
|
||||
// "I1,2c33acbcba3569f943d9e8aaea7817c5" // level-1 indirection block
|
||||
// "I3,e18604fe53ee670558eb4234d2e55cb7" // level-3 indirection block
|
||||
// "Daad048fd5721b43adaa353c407d23ff6.5617c50fb1d71b6f7a2c4c8bacacef1d2222eaa4b2245a3714686c658f8af3d9"
|
||||
// // encrypted storage block with 256-bit key
|
||||
// "I2,87381a8631dcc86256233437338e27c4.81cf86361dbc9b7905f12f6f6b80d7ec0edd487eeb339e1193805e3f58ef9505"
|
||||
// // encrypted level-2 indirection block with 256-bit key
|
||||
// "S30,50,D295754edeb35c17911b1fdf853f572fe" // section of "D295754edeb35c17911b1fdf853f572fe" between [30,80)
|
||||
//
|
||||
//
|
||||
type ObjectID struct {
|
||||
StorageBlock string
|
||||
EncryptionKey []byte
|
||||
Indirect int32
|
||||
TextContent string
|
||||
BinaryContent []byte
|
||||
@@ -82,8 +76,6 @@ type HasObjectID interface {
|
||||
// NullObjectID is the identifier of an null/empty object.
|
||||
var NullObjectID ObjectID
|
||||
|
||||
const objectIDEncryptionInfoSeparator = "."
|
||||
|
||||
var (
|
||||
inlineContentEncoding = base64.RawURLEncoding
|
||||
)
|
||||
@@ -93,17 +85,11 @@ type HasObjectID interface {
|
||||
// Note that the object ID name often contains its encryption key, which is sensitive and can be quite long (~100 characters long).
|
||||
func (oid ObjectID) String() string {
|
||||
if oid.StorageBlock != "" {
|
||||
var encryptionSuffix string
|
||||
|
||||
if len(oid.EncryptionKey) > 0 {
|
||||
encryptionSuffix = "." + hex.EncodeToString(oid.EncryptionKey)
|
||||
}
|
||||
|
||||
if oid.Indirect > 0 {
|
||||
return fmt.Sprintf("I%v,%v%v", oid.Indirect, oid.StorageBlock, encryptionSuffix)
|
||||
return fmt.Sprintf("I%v,%v", oid.Indirect, oid.StorageBlock)
|
||||
}
|
||||
|
||||
return "D" + oid.StorageBlock + encryptionSuffix
|
||||
return "D" + oid.StorageBlock
|
||||
}
|
||||
|
||||
if oid.BinaryContent != nil {
|
||||
@@ -151,11 +137,6 @@ func (oid *ObjectID) Validate() error {
|
||||
return fmt.Errorf("indirect object without storage block: %+v", oid)
|
||||
}
|
||||
|
||||
if len(oid.EncryptionKey) > 0 && len(oid.StorageBlock) == 0 {
|
||||
return fmt.Errorf("encryption key without storage block: %+v", oid)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -304,26 +285,7 @@ func ParseObjectID(s string) (ObjectID, error) {
|
||||
}
|
||||
}
|
||||
|
||||
firstSeparator := strings.Index(content, objectIDEncryptionInfoSeparator)
|
||||
lastSeparator := strings.LastIndex(content, objectIDEncryptionInfoSeparator)
|
||||
if firstSeparator == lastSeparator {
|
||||
if firstSeparator == -1 {
|
||||
// Found zero Separators in the ID - no encryption info.
|
||||
return ObjectID{StorageBlock: content, Indirect: indirectLevel}, nil
|
||||
}
|
||||
|
||||
if firstSeparator > 0 {
|
||||
b, err := hex.DecodeString(content[firstSeparator+1:])
|
||||
if err == nil && len(b) > 0 {
|
||||
// Valid chunk ID with encryption info.
|
||||
return ObjectID{
|
||||
StorageBlock: content[0:firstSeparator],
|
||||
EncryptionKey: b,
|
||||
Indirect: indirectLevel,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjectID{StorageBlock: content, Indirect: indirectLevel}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -12,14 +11,6 @@ func TestParseMalformedObjectID(t *testing.T) {
|
||||
"",
|
||||
"B!$@#$!@#$",
|
||||
"X",
|
||||
"D.",
|
||||
"D.x",
|
||||
"D.af",
|
||||
"Dx.ag",
|
||||
"Dab.",
|
||||
"Dab.a",
|
||||
"Dab.abc",
|
||||
"Dab.011",
|
||||
"I.",
|
||||
"I.x",
|
||||
"I.af",
|
||||
@@ -38,39 +29,3 @@ func TestParseMalformedObjectID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseObjectIDEncryptionInfo(t *testing.T) {
|
||||
cases := []struct {
|
||||
objectID string
|
||||
expected []byte
|
||||
}{
|
||||
{"B", nil},
|
||||
{"BAQIDBA", nil},
|
||||
{"Dabcdef", nil},
|
||||
{"I1,abcdef", nil},
|
||||
{"I2,abcdef", nil},
|
||||
{"Dabcdef.00112233", []byte{0x00, 0x11, 0x22, 0x33}},
|
||||
{"I1,abcdef.0011223344", []byte{0x00, 0x11, 0x22, 0x33, 0x44}},
|
||||
{"I2,abcdef.0011223344", []byte{0x00, 0x11, 0x22, 0x33, 0x44}},
|
||||
{"S1,2,Dabcdef.0011223344", nil},
|
||||
{"S1,2,S3,4,Dabcdef.0011223344", nil},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
objectID, err := ParseObjectID(c.objectID)
|
||||
if err != nil {
|
||||
t.Errorf("cannot parse object ID: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
actual := objectID.EncryptionKey
|
||||
if !bytes.Equal(actual, c.expected) {
|
||||
t.Errorf("invalid encryption key for %v: %x, expected: %x", c.objectID, actual, c.expected)
|
||||
}
|
||||
|
||||
uiString := objectID.String()
|
||||
if uiString != c.objectID {
|
||||
t.Errorf("invalid object ID string: %v: expected: %v", uiString, c.objectID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,24 +416,6 @@ func TestFormats(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
format: makeFormat("ENCRYPTED_HMAC_SHA512_384_AES256"),
|
||||
oids: map[string]ObjectID{
|
||||
"The quick brown fox jumps over the lazy dog": ObjectID{
|
||||
StorageBlock: "d7f4727e2c0b39ae0f1e40cc96f60242",
|
||||
EncryptionKey: mustParseBase16("d5b7801841cea6fc592c5d3e1ae50700582a96cf35e1e554995fe4e03381c237"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
format: makeFormat("ENCRYPTED_HMAC_SHA512_AES256"),
|
||||
oids: map[string]ObjectID{
|
||||
"The quick brown fox jumps over the lazy dog": ObjectID{
|
||||
StorageBlock: "b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb",
|
||||
EncryptionKey: mustParseBase16("82f948a549f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for caseIndex, c := range cases {
|
||||
@@ -473,78 +455,3 @@ func TestFormats(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidEncryptionKey(t *testing.T) {
|
||||
data := map[string][]byte{}
|
||||
st := storagetesting.NewMapStorage(data)
|
||||
format := Format{
|
||||
Version: 1,
|
||||
ObjectFormat: "ENCRYPTED_HMAC_SHA512_384_AES256",
|
||||
Secret: []byte("key"),
|
||||
MaxBlockSize: 1000,
|
||||
}
|
||||
|
||||
repo, err := New(st, &format)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create manager: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bytesToWrite := make([]byte, 1024)
|
||||
for i := range bytesToWrite {
|
||||
bytesToWrite[i] = byte(i)
|
||||
}
|
||||
|
||||
w := repo.NewWriter()
|
||||
w.Write(bytesToWrite)
|
||||
oid, err := w.Result(true)
|
||||
if err != nil {
|
||||
t.Errorf("error: %v", err)
|
||||
}
|
||||
|
||||
rc, err := repo.Open(oid)
|
||||
if err != nil || rc == nil {
|
||||
t.Errorf("error opening valid ObjectID: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Key too short
|
||||
rc, err = repo.Open(replaceEncryption(oid, oid.EncryptionKey[0:len(oid.EncryptionKey)-2]))
|
||||
if err == nil || rc != nil {
|
||||
t.Errorf("expected error when opening malformed object")
|
||||
}
|
||||
|
||||
// Key too long
|
||||
rc, err = repo.Open(replaceEncryption(oid, append(oid.EncryptionKey, 0xFF)))
|
||||
if err == nil || rc != nil {
|
||||
t.Errorf("expected error when opening malformed object")
|
||||
}
|
||||
|
||||
// Invalid key
|
||||
corruptedKey := append([]byte(nil), oid.EncryptionKey...)
|
||||
corruptedKey[0]++
|
||||
rc, err = repo.Open(replaceEncryption(oid, corruptedKey))
|
||||
if err == nil || rc != nil {
|
||||
t.Errorf("expected error when opening malformed object: %v", err)
|
||||
}
|
||||
|
||||
// Now corrupt the data
|
||||
data[oid.StorageBlock][0] ^= 1
|
||||
rc, err = repo.Open(oid)
|
||||
if err == nil || rc != nil {
|
||||
t.Errorf("expected error when opening object with corrupt data")
|
||||
}
|
||||
}
|
||||
|
||||
func replaceEncryption(oid ObjectID, newEncryption []byte) ObjectID {
|
||||
oid.EncryptionKey = newEncryption
|
||||
return oid
|
||||
}
|
||||
|
||||
func mustParseBase16(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic("invalid hex literal: " + s)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user