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:
Nick Sullivan
2015-04-09 14:50:09 -07:00
parent e24e3f3244
commit 8e910c2035
5 changed files with 266 additions and 46 deletions

View File

@@ -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)

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}