mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-06 05:27:17 +00:00
Changes to delegation model
- decryption can happen by non-admins - encrypted files can be given labels - delegation can limit decryption to specific users and labels
This commit is contained in:
17
core/core.go
17
core/core.go
@@ -34,8 +34,10 @@ type delegate struct {
|
||||
Name string
|
||||
Password string
|
||||
|
||||
Uses int
|
||||
Time string
|
||||
Uses int
|
||||
Time string
|
||||
Users []string
|
||||
Labels []string
|
||||
}
|
||||
|
||||
type password struct {
|
||||
@@ -54,6 +56,8 @@ type encrypt struct {
|
||||
LeftOwners []string
|
||||
RightOwners []string
|
||||
Data []byte
|
||||
|
||||
Labels []string
|
||||
}
|
||||
|
||||
type decrypt struct {
|
||||
@@ -228,7 +232,7 @@ func Delegate(jsonIn []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// add signed-in record to active set
|
||||
if err := keycache.AddKeyFromRecord(pr, s.Name, s.Password, s.Uses, s.Time); err != nil {
|
||||
if err := keycache.AddKeyFromRecord(pr, s.Name, s.Password, s.Users, s.Labels, s.Uses, s.Time); err != nil {
|
||||
log.Printf("Error adding key to cache for %s: %s\n", s.Name, err)
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
@@ -274,7 +278,7 @@ func Encrypt(jsonIn []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Encrypt file with list of owners
|
||||
if resp, err := cryptor.Encrypt(s.Data, s.LeftOwners, s.RightOwners, s.Minimum); err != nil {
|
||||
if resp, err := cryptor.Encrypt(s.Data, s.Labels, s.LeftOwners, s.RightOwners, s.Minimum); err != nil {
|
||||
log.Println("Error encrypting:", err)
|
||||
return jsonStatusError(err)
|
||||
} else {
|
||||
@@ -290,13 +294,12 @@ func Decrypt(jsonIn []byte) ([]byte, error) {
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
err = validateAdmin(s.Name, s.Password)
|
||||
err = validateUser(s.Name, s.Password)
|
||||
if err != nil {
|
||||
log.Println("Error validating admin status", err)
|
||||
return jsonStatusError(err)
|
||||
}
|
||||
|
||||
data, names, err := cryptor.Decrypt(s.Data)
|
||||
data, names, err := cryptor.Decrypt(s.Data, s.Name)
|
||||
if err != nil {
|
||||
log.Println("Error decrypting:", err)
|
||||
return jsonStatusError(err)
|
||||
|
||||
@@ -288,10 +288,10 @@ func TestEncryptDecrypt(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}")
|
||||
delegateJson4 := []byte("{\"Name\":\"Bob\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2}")
|
||||
delegateJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"Time\":\"10s\",\"Uses\":2}")
|
||||
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=\"}")
|
||||
encryptJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"Minumum\":2,\"Owners\":[\"Alice\",\"Bob\",\"Carol\"],\"Data\":\"SGVsbG8gSmVsbG8=\",\"Labels\":[\"blue\",\"red\"]}")
|
||||
os.Remove("/tmp/db1.json")
|
||||
|
||||
Init("/tmp/db1.json")
|
||||
|
||||
@@ -45,6 +45,7 @@ type SingleWrappedKey struct {
|
||||
type EncryptedData struct {
|
||||
Version int
|
||||
VaultId int
|
||||
Labels []string
|
||||
KeySet []MultiWrappedKey
|
||||
KeySetRSA map[string]SingleWrappedKey
|
||||
IV []byte
|
||||
@@ -112,7 +113,7 @@ func encryptKey(nameInner, nameOuter string, clearKey []byte, pubKeys map[string
|
||||
}
|
||||
|
||||
// unwrapKey decrypts first key in keys whose encryption keys are in keycache
|
||||
func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey) (unwrappedKey []byte, names []string, err error) {
|
||||
func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey, user string, labels []string) (unwrappedKey []byte, names []string, err error) {
|
||||
var (
|
||||
keyFound error
|
||||
fullMatch bool = false
|
||||
@@ -129,7 +130,7 @@ func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey) (unw
|
||||
for _, mwName := range mwKey.Name {
|
||||
pubEncrypted := pubKeys[mwName]
|
||||
// if this is null, it's an AES encrypted key
|
||||
if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, pubEncrypted.Key); keyFound != nil {
|
||||
if tmpKeyValue, keyFound = keycache.DecryptKey(tmpKeyValue, mwName, user, labels, pubEncrypted.Key); keyFound != nil {
|
||||
break
|
||||
}
|
||||
nameSet[mwName] = true
|
||||
@@ -227,6 +228,9 @@ func computeHmac(key []byte, encrypted EncryptedData) []byte {
|
||||
}
|
||||
sort.Sort(&swks)
|
||||
|
||||
// sort the labels
|
||||
sort.Strings(encrypted.Labels)
|
||||
|
||||
// start hashing
|
||||
mac.Write([]byte(strconv.Itoa(encrypted.Version)))
|
||||
mac.Write([]byte(strconv.Itoa(encrypted.VaultId)))
|
||||
@@ -249,13 +253,18 @@ func computeHmac(key []byte, encrypted EncryptedData) []byte {
|
||||
mac.Write(encrypted.IV)
|
||||
mac.Write(encrypted.Data)
|
||||
|
||||
// hash the labels
|
||||
for index := range encrypted.Labels {
|
||||
mac.Write([]byte(encrypted.Labels[index]))
|
||||
}
|
||||
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
// Encrypt encrypts data with the keys associated with names. This
|
||||
// requires a minimum of min keys to decrypt. NOTE: as currently
|
||||
// implemented, the maximum value for min is 2.
|
||||
func Encrypt(in []byte, leftNames, rightNames []string, min int) (resp []byte, err error) {
|
||||
func Encrypt(in []byte, labels, leftNames, rightNames []string, min int) (resp []byte, err error) {
|
||||
if min > 2 {
|
||||
return nil, errors.New("Minimum restricted to 2")
|
||||
}
|
||||
@@ -358,6 +367,7 @@ func Encrypt(in []byte, leftNames, rightNames []string, min int) (resp []byte, e
|
||||
aesCBC.CryptBlocks(encryptedFile, clearFile)
|
||||
|
||||
encrypted.Data = encryptedFile
|
||||
encrypted.Labels = labels
|
||||
|
||||
hmacKey, err := passvault.GetHmacKey()
|
||||
if err != nil {
|
||||
@@ -369,7 +379,7 @@ func Encrypt(in []byte, leftNames, rightNames []string, min int) (resp []byte, e
|
||||
}
|
||||
|
||||
// Decrypt decrypts a file using the keys in the key cache.
|
||||
func Decrypt(in []byte) (resp []byte, names []string, err error) {
|
||||
func Decrypt(in []byte, user string) (resp []byte, names []string, err error) {
|
||||
// unwrap encrypted file
|
||||
var encrypted EncryptedData
|
||||
if err = json.Unmarshal(in, &encrypted); err != nil {
|
||||
@@ -409,7 +419,8 @@ func Decrypt(in []byte) (resp []byte, names []string, err error) {
|
||||
|
||||
// decrypt file key with delegate keys
|
||||
var unwrappedKey = make([]byte, 16)
|
||||
if unwrappedKey, names, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA); err != nil {
|
||||
unwrappedKey, names, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA, user, encrypted.Labels)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,21 +12,29 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"github.com/cloudflare/redoctober/ecdh"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/redoctober/ecdh"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
)
|
||||
|
||||
// UserKeys is the set of decrypted keys in memory, indexed by name.
|
||||
var UserKeys map[string]ActiveUser = make(map[string]ActiveUser)
|
||||
|
||||
// Usage holds the permissions of a delegated permission
|
||||
type Usage struct {
|
||||
Uses int // Number of uses delegated
|
||||
Labels []string // File labels allowed to decrypt
|
||||
Users []string // Set of users allows to decrypt
|
||||
Expiry time.Time // Expiration of usage
|
||||
}
|
||||
|
||||
// ActiveUser holds the information about an actively delegated key.
|
||||
type ActiveUser struct {
|
||||
Admin bool
|
||||
Type string
|
||||
Expiry time.Time
|
||||
Uses int
|
||||
Usage
|
||||
Admin bool
|
||||
Type string
|
||||
|
||||
aesKey []byte
|
||||
rsaKey rsa.PrivateKey
|
||||
@@ -35,8 +43,16 @@ type ActiveUser struct {
|
||||
|
||||
// matchUser returns the matching active user if present
|
||||
// and a boolean to indicate its presence.
|
||||
func matchUser(name string) (out ActiveUser, present bool) {
|
||||
out, present = UserKeys[name]
|
||||
func matchUser(name, user string, labels []string) (out ActiveUser, present bool) {
|
||||
key, present := UserKeys[name]
|
||||
if present {
|
||||
if key.Usage.matches(user, labels) {
|
||||
return key, true
|
||||
} else {
|
||||
present = false
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -45,11 +61,47 @@ func setUser(in ActiveUser, name string) {
|
||||
UserKeys[name] = in
|
||||
}
|
||||
|
||||
// matchesLabel returns true if this usage applies the user and label
|
||||
// an empty array of Users indicates that all users are valid
|
||||
func (usage Usage) matchesLabel(labels []string) bool {
|
||||
// if asset has no labels always match
|
||||
if len(labels) == 0 {
|
||||
return true
|
||||
}
|
||||
//
|
||||
for _, validLabel := range usage.Labels {
|
||||
for _, label := range labels {
|
||||
if label == validLabel {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matches returns true if this usage applies the user and label
|
||||
// an empty array of Users indicates that all users are valid
|
||||
func (usage Usage) matches(user string, labels []string) bool {
|
||||
if !usage.matchesLabel(labels) {
|
||||
return false
|
||||
}
|
||||
// if usage lists no users, always match
|
||||
if len(usage.Users) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, validUser := range usage.Users {
|
||||
if user == validUser {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// useKey decrements the counter on an active key
|
||||
// for decryption or symmetric encryption
|
||||
func useKey(name string) {
|
||||
if val, present := matchUser(name); present {
|
||||
val.Uses -= 1
|
||||
func useKey(name, user string, labels []string) {
|
||||
if val, present := matchUser(name, user, labels); present {
|
||||
val.Usage.Uses -= 1
|
||||
setUser(val, name)
|
||||
}
|
||||
}
|
||||
@@ -69,15 +121,15 @@ func FlushCache() {
|
||||
// Refresh purges all expired or used up keys.
|
||||
func Refresh() {
|
||||
for name, active := range UserKeys {
|
||||
if active.Expiry.Before(time.Now()) || active.Uses <= 0 {
|
||||
log.Println("Record expired", name, active.Expiry)
|
||||
if active.Usage.Expiry.Before(time.Now()) || active.Usage.Uses <= 0 {
|
||||
log.Println("Record expired", name, active.Usage.Users, active.Usage.Labels, active.Usage.Expiry)
|
||||
delete(UserKeys, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddKeyFromRecord decrypts a key for a given record and adds it to the cache.
|
||||
func AddKeyFromRecord(record passvault.PasswordRecord, name string, password string, uses int, durationString string) (err error) {
|
||||
func AddKeyFromRecord(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, durationString string) (err error) {
|
||||
var current ActiveUser
|
||||
|
||||
Refresh()
|
||||
@@ -87,8 +139,10 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name string, password str
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
current.Uses = uses
|
||||
current.Expiry = time.Now().Add(duration)
|
||||
current.Usage.Uses = uses
|
||||
current.Usage.Expiry = time.Now().Add(duration)
|
||||
current.Usage.Users = users
|
||||
current.Usage.Labels = labels
|
||||
|
||||
// get decryption keys
|
||||
switch record.Type {
|
||||
@@ -127,7 +181,7 @@ func EncryptKey(in []byte, name string, override []byte) (out []byte, err error)
|
||||
|
||||
// if the override key is not set, extract from the cache
|
||||
if aesKey == nil {
|
||||
encryptKey, ok := matchUser(name)
|
||||
encryptKey, ok := matchUser(name, name, []string{})
|
||||
if !ok {
|
||||
return nil, errors.New("Key not delegated")
|
||||
}
|
||||
@@ -140,7 +194,7 @@ func EncryptKey(in []byte, name string, override []byte) (out []byte, err error)
|
||||
return out, errors.New("Require override for key")
|
||||
}
|
||||
|
||||
useKey(name)
|
||||
useKey(name, name, []string{})
|
||||
}
|
||||
|
||||
// encrypt
|
||||
@@ -159,10 +213,10 @@ func EncryptKey(in []byte, name string, override []byte) (out []byte, err error)
|
||||
// for RSA and EC keys, the cached RSA/EC key is used to decrypt
|
||||
// the pubEncryptedKey which is then used to decrypt the input
|
||||
// buffer.
|
||||
func DecryptKey(in []byte, name string, pubEncryptedKey []byte) (out []byte, err error) {
|
||||
func DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) {
|
||||
Refresh()
|
||||
|
||||
decryptKey, ok := matchUser(name)
|
||||
decryptKey, ok := matchUser(name, user, labels)
|
||||
if !ok {
|
||||
return nil, errors.New("Key not delegated")
|
||||
}
|
||||
@@ -199,7 +253,7 @@ func DecryptKey(in []byte, name string, pubEncryptedKey []byte) (out []byte, err
|
||||
out = make([]byte, 16)
|
||||
aesSession.Decrypt(out, in)
|
||||
|
||||
useKey(name)
|
||||
useKey(name, user, labels)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ var dummy = make([]byte, 16)
|
||||
|
||||
func TestUsesFlush(t *testing.T) {
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
@@ -37,7 +39,7 @@ func TestUsesFlush(t *testing.T) {
|
||||
t.Fatalf("Error in number of live keys %v", UserKeys)
|
||||
}
|
||||
|
||||
DecryptKey(dummy, "first", nil)
|
||||
DecryptKey(dummy, "first", "", []string{}, nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 0 {
|
||||
@@ -50,10 +52,12 @@ func TestTimeFlush(t *testing.T) {
|
||||
one := now.Add(oneSec)
|
||||
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Expiry: one,
|
||||
Uses: 10,
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: one,
|
||||
Uses: 10,
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
@@ -73,9 +77,157 @@ func TestTimeFlush(t *testing.T) {
|
||||
|
||||
time.Sleep(oneSec)
|
||||
|
||||
_, err := DecryptKey(dummy, "first", nil)
|
||||
_, err := DecryptKey(dummy, "first", "", []string{}, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Error in pruning expired key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoodLabel(t *testing.T) {
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
Labels: []string{"red"},
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
UserKeys["first"] = singleUse
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
EncryptKey(dummy, "first", nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
DecryptKey(dummy, "first", "", []string{"red"}, nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 0 {
|
||||
t.Fatalf("Error in number of live keys %v", UserKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadLabel(t *testing.T) {
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
Labels: []string{"red"},
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
UserKeys["first"] = singleUse
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
EncryptKey(dummy, "first", nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
_, err := DecryptKey(dummy, "first", "", []string{"blue"}, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Decryption of labeled key with no permission")
|
||||
}
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys %v", UserKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoodUser(t *testing.T) {
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
Users: []string{"ci", "buildeng", "first"},
|
||||
Labels: []string{"red", "blue"},
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
UserKeys["first"] = singleUse
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
EncryptKey(dummy, "first", nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
DecryptKey(dummy, "first", "ci", []string{"red"}, nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 0 {
|
||||
t.Fatalf("Error in number of live keys %v", UserKeys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadUser(t *testing.T) {
|
||||
singleUse := ActiveUser{
|
||||
Admin: true,
|
||||
Type: passvault.AESRecord,
|
||||
Usage: Usage{
|
||||
Expiry: nextYear,
|
||||
Uses: 2,
|
||||
Users: []string{"ci", "buildeng", "first"},
|
||||
Labels: []string{"red", "blue"},
|
||||
},
|
||||
aesKey: emptyKey,
|
||||
}
|
||||
|
||||
UserKeys["first"] = singleUse
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
// Note that the active user needs to be in the set of delegated
|
||||
// users in the AES case only
|
||||
EncryptKey(dummy, "first", nil)
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys")
|
||||
}
|
||||
|
||||
_, err := DecryptKey(dummy, "first", "", []string{"blue"}, nil)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Decryption of labeled key by unauthorized user")
|
||||
}
|
||||
|
||||
Refresh()
|
||||
if len(UserKeys) != 1 {
|
||||
t.Fatalf("Error in number of live keys %v", UserKeys)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user