mirror of
https://github.com/cloudflare/redoctober.git
synced 2025-12-23 14:25:46 +00:00
340 lines
9.7 KiB
Go
340 lines
9.7 KiB
Go
// cryptor_test.go: tests for core.go
|
|
//
|
|
// Copyright (c) 2013 CloudFlare, Inc.
|
|
|
|
package cryptor
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/cloudflare/redoctober/config"
|
|
"github.com/cloudflare/redoctober/keycache"
|
|
"github.com/cloudflare/redoctober/passvault"
|
|
"github.com/cloudflare/redoctober/persist"
|
|
)
|
|
|
|
func TestHash(t *testing.T) {
|
|
decryptJson := []byte("{\"Version\":1,\"VaultId\":529853895,\"KeySet\":[{\"Name\":[\"Bob\",\"Alice\"],\"Key\":\"2j3tI2PBFBbwFX0BlQdUuA==\"},{\"Name\":[\"Bob\",\"Carol\"],\"Key\":\"yLSSB/U6+5rc1E+gjGXT4w==\"},{\"Name\":[\"Alice\",\"Bob\"],\"Key\":\"DDlWHF7szzISuXWaEz8llQ==\"},{\"Name\":[\"Alice\",\"Carol\"],\"Key\":\"TkA13aPrYFNbveIbl0qdww==\"},{\"Name\":[\"Carol\",\"Bob\"],\"Key\":\"KXm0uObmRJ2ZvSYEWPwk2A==\"},{\"Name\":[\"Carol\",\"Alice\"],\"Key\":\"L9c+PqtxPh9y6apRvtbCQw==\"}],\"KeySetRSA\":{\"Alice\":{\"Key\":\"fj5mqnq7y5KCafCGT1I51xI5JsX746+9TTSsp/8ybf3iZjhFzSlwP3aNmsOx3SUKTmZlfs+b+MeD4eKJ2uKBFzQHAIPO0fwoiCDKHhKH6KsolNq4+jgpkLAMOLsQGs8g6BhJy6bCFRjZVc3IdlQABPM6PkTbuSvKhn9atDFwZQD5TJBISi7d2hw4LradtLITbNqwiFMTQQ9+psXzyavY8H3LNHKGgf5Od7IpthEQPCHi4nw7X/YVRTEMfoIVcMcKOwYjlC45/VJEHK9Zy3DSiLBzjmr57YNIVjw8YZY5DGBWqbgu51RUbIcrqyLphBhXoBRu4R+yrhygBNWbvkkifA==\"},\"Bob\":{\"Key\":\"HTYiZ18sf721cAN1LRNkJ/+L4AKWilMrkMyNiBjWcl9HRTVPNXITqQBXd0fBggGNPiZr6VQTySK4ZFvJKGDGiz17Te/ToDn8Yk/B9cqMsN5fHoQtXvl8IZo2wioA67ccAJ1gHMMNpPyLdF43SQqgI+XaQ2lMSYLMfxxDmBBOQ1SWAto0BDRdnsqpwUwIPKQ9Y3/1osmrjLmJoAC3MPplexYWhexNwJtSd+mFdVZ3Qe4x9RsRHcN/myihOt/67V60qzs13F0RZkMSDzj5Ddg+1KVNJZY9dmolPNkAZj8z20L9uzpatrTYTR6A8q/sRn+inO7ZQVQ00XO6q6lYYQzxnw==\"},\"Carol\":{\"Key\":\"ItrvS02nSfbcA2fl1L1i61xqPEDKRdsrYe3+UCbkT+ipheiQRPSuikbzeV2kshn4yJDeku5bmTNqW8HSGtU7GTgCoIWV8WmEf4w6ovzShPbu+VrIZvRz3wjh2oYHT/gtPVAQnBa/71FeoBNxy5l/hBcUmBky43j83Mlt2+8QZx6PEUDmpaPQemVh99+C20nQtkAUFeMc2Ge4y7RlHSxtfABvwlXx1NzCD40nyJfF1SjV/fZh/E2Al4Tavx6DOJkYGoJ2mp7XBvX0IF2tp8T3U5VpnTek/WuNrLL9z7/jqzWh87lZ5KheWXhGkU1BNH4lfIj43pDkSy50aDvS0zYfHQ==\"}},\"IV\":\"58r9Mz8e06mItBG9nSV/0Q==\",\"Data\":\"QE9ZhcGXNXauUdMk04biUGy1SoP5H2nF/j2JjiiVFKPdIdRp/Gc+AZvUI9n22ZM4q+zDiJz7qvK4bKaPpXhTmGP0XheaFUukeVNS9STMoTbNcY/ZtVOz6hizUPF7gSq388QPUsT+Axml3rEUTWOhnw==\",\"Signature\":\"ItiAS26GFlbM5szJr5HXVB9BR+s=\"}")
|
|
|
|
var encrypted EncryptedData
|
|
if err := json.Unmarshal(decryptJson, &encrypted); err != nil {
|
|
t.Fatalf("Error unmarshalling json, %v", err)
|
|
}
|
|
|
|
var hmacKey, _ = base64.StdEncoding.DecodeString("Qugc5ZQ0vC7KQSgmDHTVgQ==")
|
|
var signature = append([]byte{}, encrypted.Signature...)
|
|
|
|
expectedSig := encrypted.computeHmac(hmacKey)
|
|
|
|
if diff := bytes.Compare(signature, expectedSig); diff != 0 {
|
|
t.Fatalf("Error comparing signature %v", base64.StdEncoding.EncodeToString(expectedSig))
|
|
}
|
|
|
|
// change version and check hmac
|
|
encrypted.Version = 2
|
|
unexpectedSig := encrypted.computeHmac(hmacKey)
|
|
|
|
if diff := bytes.Compare(signature, unexpectedSig); diff == 0 {
|
|
t.Fatalf("Error comparing signature")
|
|
}
|
|
encrypted.Version = 1
|
|
|
|
// change vaultid and check hmac
|
|
encrypted.VaultId = 529853896
|
|
unexpectedSig = encrypted.computeHmac(hmacKey)
|
|
|
|
if diff := bytes.Compare(signature, unexpectedSig); diff == 0 {
|
|
t.Fatalf("Error comparing signature")
|
|
}
|
|
encrypted.VaultId = 529853895
|
|
|
|
// swap two records and check hmac
|
|
encrypted.KeySet[0], encrypted.KeySet[1] = encrypted.KeySet[1], encrypted.KeySet[0]
|
|
unexpectedSig = encrypted.computeHmac(hmacKey)
|
|
|
|
if diff := bytes.Compare(signature, unexpectedSig); diff != 0 {
|
|
t.Fatalf("Error comparing signature %v, %v",
|
|
base64.StdEncoding.EncodeToString(unexpectedSig),
|
|
base64.StdEncoding.EncodeToString(signature))
|
|
}
|
|
|
|
// delete RSA key and check hmac
|
|
encrypted.Version = 1
|
|
delete(encrypted.KeySetRSA, "Carol")
|
|
unexpectedSig = encrypted.computeHmac(hmacKey)
|
|
|
|
if diff := bytes.Compare(signature, unexpectedSig); diff == 0 {
|
|
t.Fatalf("Error comparing signature")
|
|
}
|
|
|
|
}
|
|
|
|
func TestDuplicates(t *testing.T) {
|
|
// Setup total names and partitions.
|
|
names := []string{"Alice", "Bob", "Carl"}
|
|
recs := make(map[string]passvault.PasswordRecord, 0)
|
|
left := []string{"Alice", "Bob"}
|
|
right := []string{"Bob", "Carl"}
|
|
|
|
// Add each user to the keycache.
|
|
cache := keycache.NewCache()
|
|
records, err := passvault.InitFrom("memory")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
cfg := &config.Delegations{Persist: false}
|
|
store, err := persist.New(cfg)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
c := Cryptor{&records, &cache, store}
|
|
|
|
for _, name := range names {
|
|
pr, err := records.AddNewRecord(name, "weakpassword", true, passvault.DefaultRecordType)
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
recs[name] = pr
|
|
}
|
|
|
|
// Create candidate encryption of message.
|
|
ac := AccessStructure{
|
|
LeftNames: left,
|
|
RightNames: right,
|
|
}
|
|
|
|
resp, err := c.Encrypt([]byte("Hello World!"), []string{}, []string{}, ac)
|
|
if err != nil {
|
|
t.Fatalf("Error: %s", err)
|
|
}
|
|
|
|
// Delegate one key at a time and check that decryption fails.
|
|
for name, pr := range recs {
|
|
err = cache.AddKeyFromRecord(pr, name, "weakpassword", nil, nil, 2, "", "1h")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
_, _, _, _, _, err := c.Decrypt(resp, name)
|
|
if err == nil {
|
|
t.Fatalf("That shouldn't have worked!")
|
|
}
|
|
|
|
cache.Flush()
|
|
}
|
|
}
|
|
|
|
func TestEncryptDecrypt(t *testing.T) {
|
|
// Setup total names and partitions.
|
|
names := []string{"Alice", "Bob", "Carl"}
|
|
recs := make(map[string]passvault.PasswordRecord, 0)
|
|
left := []string{"Alice", "Bob"}
|
|
right := []string{"Bob", "Carl"}
|
|
|
|
// Add each user to the keycache.
|
|
cache := keycache.NewCache()
|
|
records, err := passvault.InitFrom("memory")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
cfg := &config.Delegations{Persist: false}
|
|
store, err := persist.New(cfg)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
c := Cryptor{&records, &cache, store}
|
|
|
|
for _, name := range names {
|
|
pr, err := records.AddNewRecord(name, "weakpassword", true, passvault.DefaultRecordType)
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
recs[name] = pr
|
|
}
|
|
|
|
// Create candidate encryption of message.
|
|
ac := AccessStructure{
|
|
LeftNames: left,
|
|
RightNames: right,
|
|
}
|
|
|
|
resp, err := c.Encrypt([]byte("Hello World!"), []string{}, []string{}, ac)
|
|
if err != nil {
|
|
t.Fatalf("Error: %s", err)
|
|
}
|
|
|
|
// Delegate all the things.
|
|
for name, pr := range recs {
|
|
err = cache.AddKeyFromRecord(pr, name, "weakpassword", nil, nil, 2, "", "1h")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
// (resp []byte, labels, names []string, secure bool, err error)
|
|
_, _, _, _, _, err = c.Decrypt(resp, "alice")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func tempName() (string, error) {
|
|
tmpf, err := ioutil.TempFile("", "ro_cryptor")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
name := tmpf.Name()
|
|
tmpf.Close()
|
|
return name, nil
|
|
}
|
|
|
|
func TestRestore(t *testing.T) {
|
|
const testUses = 5 // How many uses to delegate for.
|
|
|
|
// Get the temporary persisted file.
|
|
temp, err := tempName()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(temp)
|
|
|
|
// Setup total names and partitions.
|
|
names := []string{"Alice", "Bob", "Carl"}
|
|
recs := make(map[string]passvault.PasswordRecord, 0)
|
|
|
|
// Add each user to the keycache.
|
|
cache := keycache.NewCache()
|
|
records, err := passvault.InitFrom("memory")
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
for _, name := range names {
|
|
pr, err := records.AddNewRecord(name, "weakpassword", true, passvault.DefaultRecordType)
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
recs[name] = pr
|
|
}
|
|
|
|
alice, ok := records.GetRecord("Alice")
|
|
if !ok {
|
|
t.Fatal("Alice not found in password vault.")
|
|
}
|
|
|
|
carl, ok := records.GetRecord("Carl")
|
|
if !ok {
|
|
t.Fatal("Carl not found in password vault.")
|
|
}
|
|
|
|
// First, simulate a running Red October with persistence.
|
|
cfg := &config.Delegations{
|
|
Persist: true,
|
|
Mechanism: persist.FileMechanism,
|
|
Location: temp,
|
|
Policy: "(Alice & Bob) | (Bob & Carl)",
|
|
Users: []string{"Alice", "Bob", "Carl"},
|
|
}
|
|
|
|
store, err := persist.New(cfg)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
c := Cryptor{&records, &cache, store}
|
|
c.persist.Persist()
|
|
|
|
err = c.Delegate(alice, "Alice", "weakpassword", []string{"Bob"}, []string{},
|
|
testUses, "", "1h")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = c.Delegate(carl, "Carl", "weakpassword", []string{"Bob"}, []string{},
|
|
testUses, "", "1h")
|
|
|
|
// Next, simulate restarting that server.
|
|
store, err = persist.New(cfg)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
c = Cryptor{&records, &cache, store}
|
|
if _, err := os.Stat(temp); err != nil {
|
|
t.Fatalf("Not persisting: %v", err)
|
|
}
|
|
|
|
err = c.Restore("Alice", "weakpassword", 2, "", "1h")
|
|
if err != ErrRestoreDelegations {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = c.Restore("Carl", "weakpassword", 2, "", "1h")
|
|
if err != ErrRestoreDelegations {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
status := c.Status()
|
|
if status.State != persist.Inactive {
|
|
t.Fatalf("The persistent delegations should be %s, not %s",
|
|
persist.Inactive, status.State)
|
|
}
|
|
|
|
err = c.Restore("Carl", "weakpassword", 0, "", "0h")
|
|
if err != ErrRestoreDelegations {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = c.Restore("Bob", "weakpassword", 2, "", "1h")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
status = c.Status()
|
|
if status.State != persist.Active {
|
|
t.Fatalf("The persistent delegations should be %s, not %s",
|
|
persist.Active, status.State)
|
|
}
|
|
|
|
if len(c.cache.UserKeys) != 2 {
|
|
t.Fatal("Delegations do not seem to have been restored.")
|
|
}
|
|
|
|
usage, ok := c.cache.UserKeys[keycache.DelegateIndex{Name: "Alice"}]
|
|
if !ok {
|
|
t.Fatal("Alice not found in active delegations.")
|
|
}
|
|
|
|
if usage.Uses != testUses {
|
|
t.Fatal("Invalid number of uses in restored delegations.")
|
|
}
|
|
|
|
usage, ok = c.cache.UserKeys[keycache.DelegateIndex{Name: "Carl"}]
|
|
if !ok {
|
|
t.Fatal("Carl not found in active delegations.")
|
|
}
|
|
|
|
if usage.Uses != testUses {
|
|
t.Fatal("Invalid number of uses in restored delegations.")
|
|
}
|
|
|
|
_, ok = c.cache.UserKeys[keycache.DelegateIndex{Name: "Bob"}]
|
|
if ok {
|
|
t.Fatal("Bob shouldn't be in the active delegations.")
|
|
}
|
|
|
|
}
|