mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-05 04:56:07 +00:00
Allow encryption with a single owner
The "minimum" parameter was ignored, so when a single user was provided as an owner, the encrypted data had no KeySet value and could not be decrypted. This change fixes the API and cryptor to pay attention to the Minimum parameter and handle the case where Minimum is 1
This commit is contained in:
@@ -69,6 +69,7 @@ type EncryptRequest struct {
|
||||
Name string
|
||||
Password string
|
||||
|
||||
Minimum int
|
||||
Owners []string
|
||||
LeftOwners []string
|
||||
RightOwners []string
|
||||
@@ -453,6 +454,7 @@ func Encrypt(jsonIn []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
access := cryptor.AccessStructure{
|
||||
Minimum: s.Minimum,
|
||||
Names: s.Owners,
|
||||
LeftNames: s.LeftOwners,
|
||||
RightNames: s.RightOwners,
|
||||
@@ -497,6 +499,7 @@ func ReEncrypt(jsonIn []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
access := cryptor.AccessStructure{
|
||||
Minimum: s.Minimum,
|
||||
Names: s.Owners,
|
||||
LeftNames: s.LeftOwners,
|
||||
RightNames: s.RightOwners,
|
||||
|
||||
@@ -400,8 +400,10 @@ func TestEncryptDecrypt(t *testing.T) {
|
||||
delegateJson3 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
|
||||
delegateJson4 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2,\"Users\":[\"Alice\"],\"Labels\":[\"blue\"]}")
|
||||
delegateJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2,\"Users\":[\"Alice\"],\"Labels\":[\"blue\"]}")
|
||||
encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\",\"Labels\":[\"blue\",\"red\"]}")
|
||||
delegateJson6 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":1}")
|
||||
encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minimum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\",\"Labels\":[\"blue\",\"red\"]}")
|
||||
encryptJson3 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minimum\":1,\"Owners\":[\"Alice\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
|
||||
Init("memory")
|
||||
|
||||
@@ -575,6 +577,49 @@ func TestEncryptDecrypt(t *testing.T) {
|
||||
t.Fatalf("Error in decrypt, %v", d.Delegates)
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt with only a single owner
|
||||
respJson, err = Encrypt(encryptJson3)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in encrypt, %v", err)
|
||||
}
|
||||
err = json.Unmarshal(respJson, &s)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in encrypt, %v", err)
|
||||
}
|
||||
if s.Status != "ok" {
|
||||
t.Fatalf("Error in encrypt, %v", s.Status)
|
||||
}
|
||||
|
||||
respJson, err = Delegate(delegateJson6)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in delegating account, %v", err)
|
||||
}
|
||||
err = json.Unmarshal(respJson, &s)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in delegating account, %v", err)
|
||||
}
|
||||
if s.Status != "ok" {
|
||||
t.Fatalf("Error in delegating account, %v", s.Status)
|
||||
}
|
||||
|
||||
// decrypt file
|
||||
decryptJson2, err := json.Marshal(DecryptRequest{Name: "Alice", Password: "Hello", Data: s.Response})
|
||||
if err != nil {
|
||||
t.Fatalf("Error in marshalling decryption, %v", err)
|
||||
}
|
||||
|
||||
respJson2, err = Decrypt(decryptJson2)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in decrypt, %v", err)
|
||||
}
|
||||
err = json.Unmarshal(respJson2, &s)
|
||||
if err != nil {
|
||||
t.Fatalf("Error in decrypt, %v", err)
|
||||
}
|
||||
if s.Status != "ok" {
|
||||
t.Fatalf("Error in decrypt, %v", s.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReEncrypt(t *testing.T) {
|
||||
@@ -585,7 +630,7 @@ func TestReEncrypt(t *testing.T) {
|
||||
delegateJson5 := []byte(`{"Name":"Carol","Password":"Hello","Time":"10s","Uses":2,"Users":["Alice"],"Labels":["blue"]}`)
|
||||
delegateJson6 := []byte(`{"Name":"Bob","Password":"Hello","Time":"10s","Uses":2,"Users":["Alice"],"Labels":["red"]}`)
|
||||
delegateJson7 := []byte(`{"Name":"Carol","Password":"Hello","Time":"10s","Uses":2,"Users":["Alice"],"Labels":["red"]}`)
|
||||
encryptJson := []byte(`{"Name":"Carol","Password":"Hello","Minumum":2,"Owners":["Alice","Bob","Carol"],"Data":"SGVsbG8gSmVsbG8=","Labels":["blue"]}`)
|
||||
encryptJson := []byte(`{"Name":"Carol","Password":"Hello","Minimum":2,"Owners":["Alice","Bob","Carol"],"Data":"SGVsbG8gSmVsbG8=","Labels":["blue"]}`)
|
||||
|
||||
Init("memory")
|
||||
|
||||
@@ -671,6 +716,7 @@ func TestReEncrypt(t *testing.T) {
|
||||
Name: "Alice",
|
||||
Password: "Hello",
|
||||
Data: s.Response,
|
||||
Minimum: 2,
|
||||
Owners: []string{"Alice", "Bob", "Carol"},
|
||||
Labels: []string{"red"},
|
||||
})
|
||||
@@ -761,7 +807,7 @@ func TestOwners(t *testing.T) {
|
||||
delegateJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
|
||||
delegateJson2 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
|
||||
delegateJson3 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"0s\",\"Uses\":0}")
|
||||
encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
encryptJson := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Minimum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\"}")
|
||||
|
||||
var s ResponseData
|
||||
var l OwnersData
|
||||
|
||||
@@ -42,7 +42,8 @@ func New(records *passvault.Records, cache *keycache.Cache) Cryptor {
|
||||
// both, then he can decrypt it alone). If a predicate is present, it must be
|
||||
// satisfied to decrypt.
|
||||
type AccessStructure struct {
|
||||
Names []string
|
||||
Minimum int
|
||||
Names []string
|
||||
|
||||
LeftNames []string
|
||||
RightNames []string
|
||||
@@ -253,23 +254,19 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b
|
||||
return
|
||||
}
|
||||
|
||||
encryptKey := func(outer, inner string, clearKey []byte) (keyBytes []byte, err error) {
|
||||
var outerCrypt, innerCrypt cipher.Block
|
||||
encryptKey := func(keyNames []string, clearKey []byte) (keyBytes []byte, err error) {
|
||||
keyBytes = make([]byte, 16)
|
||||
copy(keyBytes, clearKey)
|
||||
for _, keyName := range keyNames {
|
||||
var keyCrypt cipher.Block
|
||||
keyCrypt, err = aes.NewCipher(encrypted.KeySetRSA[keyName].aesKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
outerCrypt, err = aes.NewCipher(encrypted.KeySetRSA[outer].aesKey)
|
||||
if err != nil {
|
||||
return
|
||||
keyCrypt.Encrypt(keyBytes, keyBytes)
|
||||
}
|
||||
|
||||
innerCrypt, err = aes.NewCipher(encrypted.KeySetRSA[inner].aesKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
innerCrypt.Encrypt(keyBytes, clearKey)
|
||||
outerCrypt.Encrypt(keyBytes, keyBytes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -282,26 +279,40 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// encrypt file key with every combination of two keys
|
||||
encrypted.KeySet = make([]MultiWrappedKey, 0)
|
||||
|
||||
for i := 0; i < len(access.Names); i++ {
|
||||
for j := i + 1; j < len(access.Names); j++ {
|
||||
keyBytes, err := encryptKey(access.Names[i], access.Names[j], clearKey)
|
||||
if access.Minimum == 1 {
|
||||
keyBytes, err := encryptKey([]string{access.Names[0]}, clearKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := MultiWrappedKey{
|
||||
Name: []string{access.Names[i], access.Names[j]},
|
||||
encrypted.KeySet = append(encrypted.KeySet, MultiWrappedKey{
|
||||
Name: []string{access.Names[0]},
|
||||
Key: keyBytes,
|
||||
}
|
||||
|
||||
encrypted.KeySet = append(encrypted.KeySet, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if access.Minimum == 2 {
|
||||
for i := 0; i < len(access.Names); i++ {
|
||||
for j := i + 1; j < len(access.Names); j++ {
|
||||
keyBytes, err := encryptKey([]string{access.Names[j], access.Names[i]}, clearKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := MultiWrappedKey{
|
||||
Name: []string{access.Names[i], access.Names[j]},
|
||||
Key: keyBytes,
|
||||
}
|
||||
|
||||
encrypted.KeySet = append(encrypted.KeySet, out)
|
||||
}
|
||||
}
|
||||
} else if access.Minimum > 3 {
|
||||
err = errors.New("Encryption to a list of owners with minimum > 2 is not implemented")
|
||||
return err
|
||||
}
|
||||
} else if len(access.LeftNames) > 0 && len(access.RightNames) > 0 {
|
||||
// Generate a random AES key for each user and RSA/ECIES encrypt it
|
||||
encrypted.KeySetRSA = make(map[string]SingleWrappedKey)
|
||||
@@ -329,7 +340,7 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b
|
||||
continue
|
||||
}
|
||||
|
||||
keyBytes, err := encryptKey(leftName, rightName, clearKey)
|
||||
keyBytes, err := encryptKey([]string{rightName, leftName}, clearKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -33,19 +33,20 @@ var (
|
||||
createUserInput3 = &core.CreateUserRequest{Name: "Dodo", Password: "Dodgson"}
|
||||
|
||||
delegateInput1 = &core.DelegateRequest{
|
||||
Name: createUserInput1.Name,
|
||||
Name: createUserInput1.Name,
|
||||
Password: createUserInput1.Password,
|
||||
Time: "2h34m",
|
||||
Uses: 1,
|
||||
Time: "2h34m",
|
||||
Uses: 1,
|
||||
}
|
||||
delegateInput2 = &core.DelegateRequest{
|
||||
Name: createUserInput2.Name,
|
||||
Name: createUserInput2.Name,
|
||||
Password: createUserInput2.Password,
|
||||
Time: "2h34m",
|
||||
Uses: 1,
|
||||
Time: "2h34m",
|
||||
Uses: 1,
|
||||
}
|
||||
|
||||
encryptInput = &core.EncryptRequest{
|
||||
encryptInput = &core.EncryptRequest{
|
||||
Minimum: 2,
|
||||
Name: createVaultInput.Name,
|
||||
Password: createVaultInput.Password,
|
||||
Owners: []string{createUserInput1.Name, createUserInput2.Name},
|
||||
@@ -378,9 +379,9 @@ func TestDecrypt(t *testing.T) {
|
||||
encryptedData := s.Response
|
||||
|
||||
decryptInput := &core.DecryptRequest{
|
||||
Name: "Alice",
|
||||
Name: "Alice",
|
||||
Password: "Lewis",
|
||||
Data: encryptedData,
|
||||
Data: encryptedData,
|
||||
}
|
||||
|
||||
// Check the first decrypt command (where not enough owners have decrypted yet).
|
||||
@@ -612,8 +613,8 @@ func TestPassword(t *testing.T) {
|
||||
|
||||
// Check changing password with invalid password.
|
||||
passwordInput := &core.PasswordRequest{
|
||||
Name: createUserInput1.Name,
|
||||
Password: "badpassword",
|
||||
Name: createUserInput1.Name,
|
||||
Password: "badpassword",
|
||||
NewPassword: "worsepassword",
|
||||
}
|
||||
if err := postAndTest("password", passwordInput, 200, "Wrong Password"); err != nil {
|
||||
@@ -623,8 +624,8 @@ func TestPassword(t *testing.T) {
|
||||
|
||||
// Check changing password with nonexistent user.
|
||||
passwordInput = &core.PasswordRequest{
|
||||
Name: createUserInput2.Name,
|
||||
Password: "badpassword",
|
||||
Name: createUserInput2.Name,
|
||||
Password: "badpassword",
|
||||
NewPassword: "worsepassword",
|
||||
}
|
||||
if err := postAndTest("password", passwordInput, 200, "Record not present"); err != nil {
|
||||
@@ -634,8 +635,8 @@ func TestPassword(t *testing.T) {
|
||||
|
||||
// Check changing the password properly.
|
||||
passwordInput = &core.PasswordRequest{
|
||||
Name: createUserInput1.Name,
|
||||
Password: createUserInput1.Password,
|
||||
Name: createUserInput1.Name,
|
||||
Password: createUserInput1.Password,
|
||||
NewPassword: "foobar",
|
||||
}
|
||||
if err := postAndTest("password", passwordInput, 200, "ok"); err != nil {
|
||||
@@ -662,7 +663,7 @@ func TestPurge(t *testing.T) {
|
||||
|
||||
// Check purging with non-admin user
|
||||
purgeInput := &core.PurgeRequest{
|
||||
Name: createUserInput1.Name,
|
||||
Name: createUserInput1.Name,
|
||||
Password: createUserInput1.Password,
|
||||
}
|
||||
if err := postAndTest("purge", purgeInput, 200, "Admin required"); err != nil {
|
||||
@@ -672,7 +673,7 @@ func TestPurge(t *testing.T) {
|
||||
|
||||
// Check purging with admin user
|
||||
purgeInput = &core.PurgeRequest{
|
||||
Name: createVaultInput.Name,
|
||||
Name: createVaultInput.Name,
|
||||
Password: createVaultInput.Password,
|
||||
}
|
||||
if err := postAndTest("purge", purgeInput, 200, "ok"); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user