diff --git a/core/core.go b/core/core.go index 1aac20e..b182026 100644 --- a/core/core.go +++ b/core/core.go @@ -15,6 +15,12 @@ import ( "github.com/cloudflare/redoctober/passvault" ) +var ( + crypt cryptor.Cryptor + records passvault.Records + cache keycache.Cache +) + // Each of these structures corresponds to the JSON expected on the // correspondingly named URI (e.g. the delegate structure maps to the // JSON that should be sent on the /delegate URI and it is handled by @@ -51,11 +57,11 @@ type EncryptRequest struct { Name string Password string - Minimum int Owners []string LeftOwners []string RightOwners []string - Data []byte + + Data []byte Labels []string } @@ -90,6 +96,7 @@ type SummaryData struct { type DecryptWithDelegates struct { Data []byte + Secure bool Delegates []string } @@ -102,7 +109,7 @@ func jsonStatusError(err error) ([]byte, error) { return json.Marshal(ResponseData{Status: err.Error()}) } func jsonSummary() ([]byte, error) { - return json.Marshal(SummaryData{Status: "ok", Live: keycache.GetSummary(), All: passvault.GetSummary()}) + return json.Marshal(SummaryData{Status: "ok", Live: cache.GetSummary(), All: records.GetSummary()}) } func jsonResponse(resp []byte) ([]byte, error) { return json.Marshal(ResponseData{Status: "ok", Response: resp}) @@ -111,11 +118,11 @@ func jsonResponse(resp []byte) ([]byte, error) { // validateAdmin checks that the username and password passed in are // correct and that the user is an admin func validateAdmin(name, password string) error { - if passvault.NumRecords() == 0 { + if records.NumRecords() == 0 { return errors.New("Vault is not created yet") } - pr, ok := passvault.GetRecord(name) + pr, ok := records.GetRecord(name) if !ok { return errors.New("User not present") } @@ -144,9 +151,13 @@ func validateUser(name, password string) error { // Init reads the records from disk from a given path func Init(path string) (err error) { - if err = passvault.InitFromDisk(path); err != nil { + if records, err = passvault.InitFrom(path); err != nil { err = fmt.Errorf("Failed to load password vault %s: %s", path, err) } + + cache = keycache.Cache{make(map[string]keycache.ActiveUser)} + crypt = cryptor.New(&records, &cache) + return } @@ -157,7 +168,7 @@ func Create(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if passvault.NumRecords() != 0 { + if records.NumRecords() != 0 { return jsonStatusError(errors.New("Vault is already created")) } @@ -166,7 +177,7 @@ func Create(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if _, err := passvault.AddNewRecord(s.Name, s.Password, true); err != nil { + if _, err := records.AddNewRecord(s.Name, s.Password, true, passvault.DefaultRecordType); err != nil { log.Printf("Error adding record for %s: %s\n", s.Name, err) return jsonStatusError(err) } @@ -177,13 +188,13 @@ func Create(jsonIn []byte) ([]byte, error) { // Summary processes a summary request. func Summary(jsonIn []byte) ([]byte, error) { var s SummaryRequest - keycache.Refresh() + cache.Refresh() if err := json.Unmarshal(jsonIn, &s); err != nil { return jsonStatusError(err) } - if passvault.NumRecords() == 0 { + if records.NumRecords() == 0 { return jsonStatusError(errors.New("Vault is not created yet")) } @@ -202,7 +213,7 @@ func Delegate(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if passvault.NumRecords() == 0 { + if records.NumRecords() == 0 { return jsonStatusError(errors.New("Vault is not created yet")) } @@ -214,21 +225,21 @@ func Delegate(jsonIn []byte) ([]byte, error) { // Find password record for user and verify that their password // matches. If not found then add a new entry for this user. - pr, found := passvault.GetRecord(s.Name) + pr, found := records.GetRecord(s.Name) if found { if err := pr.ValidatePassword(s.Password); err != nil { return jsonStatusError(err) } } else { var err error - if pr, err = passvault.AddNewRecord(s.Name, s.Password, false); err != nil { + if pr, err = records.AddNewRecord(s.Name, s.Password, false, passvault.DefaultRecordType); err != nil { log.Printf("Error adding record for %s: %s\n", s.Name, err) return jsonStatusError(err) } } // add signed-in record to active set - if err := keycache.AddKeyFromRecord(pr, s.Name, s.Password, s.Users, s.Labels, s.Uses, s.Time); err != nil { + if err := cache.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) } @@ -243,12 +254,12 @@ func Password(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if passvault.NumRecords() == 0 { + if records.NumRecords() == 0 { return jsonStatusError(errors.New("Vault is not created yet")) } // add signed-in record to active set - if err := passvault.ChangePassword(s.Name, s.Password, s.NewPassword); err != nil { + if err := records.ChangePassword(s.Name, s.Password, s.NewPassword); err != nil { log.Println("Error changing password:", err) return jsonStatusError(err) } @@ -268,13 +279,9 @@ func Encrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if len(s.Owners) > 0 { - s.LeftOwners = s.Owners - s.RightOwners = s.Owners - } - - // Encrypt file with list of owners - if resp, err := cryptor.Encrypt(s.Data, s.Labels, s.LeftOwners, s.RightOwners, s.Minimum); err != nil { + // Encrypt file + access := cryptor.AccessStructure{s.Owners, s.LeftOwners, s.RightOwners} + if resp, err := crypt.Encrypt(s.Data, s.Labels, access); err != nil { log.Println("Error encrypting:", err) return jsonStatusError(err) } else { @@ -296,7 +303,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - data, names, err := cryptor.Decrypt(s.Data, s.Name) + data, names, secure, err := crypt.Decrypt(s.Data, s.Name) if err != nil { log.Println("Error decrypting:", err) return jsonStatusError(err) @@ -304,6 +311,7 @@ func Decrypt(jsonIn []byte) ([]byte, error) { resp := &DecryptWithDelegates{ Data: data, + Secure: secure, Delegates: names, } @@ -328,7 +336,7 @@ func Modify(jsonIn []byte) ([]byte, error) { return jsonStatusError(err) } - if _, ok := passvault.GetRecord(s.ToModify); !ok { + if _, ok := records.GetRecord(s.ToModify); !ok { return jsonStatusError(errors.New("Record to modify missing")) } @@ -339,11 +347,11 @@ func Modify(jsonIn []byte) ([]byte, error) { var err error switch s.Command { case "delete": - err = passvault.DeleteRecord(s.ToModify) + err = records.DeleteRecord(s.ToModify) case "revoke": - err = passvault.RevokeRecord(s.ToModify) + err = records.RevokeRecord(s.ToModify) case "admin": - err = passvault.MakeAdmin(s.ToModify) + err = records.MakeAdmin(s.ToModify) default: return jsonStatusError(errors.New("Unknown command")) } diff --git a/core/core_test.go b/core/core_test.go index 4acadcc..688c8b6 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -10,15 +10,13 @@ import ( "os" "testing" - "github.com/cloudflare/redoctober/keycache" "github.com/cloudflare/redoctober/passvault" ) func TestCreate(t *testing.T) { createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}") - os.Remove("/tmp/db1.json") - Init("/tmp/db1.json") + Init("memory") respJson, err := Create(createJson) if err != nil { @@ -46,14 +44,11 @@ func TestCreate(t *testing.T) { if s.Status == "ok" { t.Fatalf("Error in creating account when one exists, %v", s.Status) } - - os.Remove("/tmp/db1.json") } func TestSummary(t *testing.T) { createJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\"}") delegateJson := []byte("{\"Name\":\"Bob\",\"Password\":\"Rob\",\"Time\":\"2h\",\"Uses\":1}") - os.Remove("/tmp/db1.json") // check for summary of uninitialized vault respJson, err := Summary(createJson) @@ -69,7 +64,7 @@ func TestSummary(t *testing.T) { t.Fatalf("Error in summary of account with no vault, %v", s.Status) } - Init("/tmp/db1.json") + Init("memory") // check for summary of initialized vault respJson, err = Create(createJson) @@ -156,7 +151,7 @@ func TestSummary(t *testing.T) { dataLive, ok := s.Live["Bob"] if !ok { - t.Fatalf("Error in summary of account, record missing, %v", keycache.UserKeys) + t.Fatalf("Error in summary of account, record missing, %v", cache.UserKeys) } if dataLive.Admin != false { t.Fatalf("Error in summary of account, record missing") @@ -164,10 +159,6 @@ func TestSummary(t *testing.T) { if dataLive.Type != passvault.DefaultRecordType { t.Fatalf("Error in summary of account, record missing") } - - keycache.FlushCache() - - os.Remove("/tmp/db1.json") } func TestPassword(t *testing.T) { @@ -176,9 +167,8 @@ func TestPassword(t *testing.T) { passwordJson := []byte("{\"Name\":\"Alice\",\"Password\":\"Hello\",\"NewPassword\":\"Olleh\"}") delegateJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"Time\":\"2h\",\"Uses\":1}") passwordJson2 := []byte("{\"Name\":\"Alice\",\"Password\":\"Olleh\",\"NewPassword\":\"Hello\"}") - os.Remove("/tmp/db1.json") - Init("/tmp/db1.json") + Init("memory") // check for summary of initialized vault with new member var s ResponseData @@ -277,10 +267,6 @@ func TestPassword(t *testing.T) { if s.Status != "ok" { t.Fatalf("Error in delegating account, %v", s.Status) } - - keycache.FlushCache() - - os.Remove("/tmp/db1.json") } func TestEncryptDecrypt(t *testing.T) { @@ -292,9 +278,8 @@ func TestEncryptDecrypt(t *testing.T) { 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\"]}") - os.Remove("/tmp/db1.json") - Init("/tmp/db1.json") + Init("memory") // check for summary of initialized vault with new member var s ResponseData @@ -335,7 +320,7 @@ func TestEncryptDecrypt(t *testing.T) { } // check summary to see if none are delegated - keycache.Refresh() + cache.Refresh() respJson, err = Summary(summaryJson) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -422,7 +407,7 @@ func TestEncryptDecrypt(t *testing.T) { } // verify the presence of the two delgations - keycache.Refresh() + cache.Refresh() var sum2 SummaryData respJson, err = Summary(summaryJson) if err != nil { @@ -466,10 +451,6 @@ func TestEncryptDecrypt(t *testing.T) { t.Fatalf("Error in decrypt, %v", d.Delegates) } } - - keycache.FlushCache() - - os.Remove("/tmp/db1.json") } func TestModify(t *testing.T) { @@ -484,8 +465,7 @@ func TestModify(t *testing.T) { modifyJson4 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"revoke\"}") modifyJson5 := []byte("{\"Name\":\"Carol\",\"Password\":\"Hello\",\"ToModify\":\"Alice\",\"Command\":\"delete\"}") - os.Remove("/tmp/db1.json") - Init("/tmp/db1.json") + Init("memory") // check for summary of initialized vault with new member var s ResponseData @@ -526,7 +506,7 @@ func TestModify(t *testing.T) { } // check summary to see if none are delegated - keycache.Refresh() + cache.Refresh() respJson, err = Summary(summaryJson) if err != nil { t.Fatalf("Error in summary, %v", err) @@ -653,10 +633,6 @@ func TestModify(t *testing.T) { if len(sum3.All) != 2 { t.Fatalf("Error in summary, %v", sum3.All) } - - keycache.FlushCache() - - os.Remove("/tmp/db1.json") } func TestStatic(t *testing.T) { @@ -731,7 +707,7 @@ func TestStatic(t *testing.T) { t.Fatalf("Error in summary, %v, %v", expected, r.Response) } - keycache.FlushCache() + cache.FlushCache() os.Remove("/tmp/db1.json") } diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index ecb4613..0d27cd5 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -25,6 +25,27 @@ const ( DEFAULT_VERSION = 1 ) +type Cryptor struct { + records *passvault.Records + cache *keycache.Cache +} + +func New(records *passvault.Records, cache *keycache.Cache) Cryptor { + return Cryptor{records, cache} +} + +// AccessStructure represents different possible access structures for +// encrypted data. If len(Names) > 0, then at least 2 of the users in the list +// must be delegated to decrypt. If len(LeftNames) > 0 & len(RightNames) > 0, +// then at least one from each list must be delegated (if the same user is in +// both, then he can decrypt it alone). +type AccessStructure struct { + Names []string + + LeftNames []string + RightNames []string +} + // MultiWrappedKey is a structure containing a 16-byte key encrypted // once for each of the keys corresponding to the names of the users // in Name in order. @@ -44,185 +65,56 @@ type SingleWrappedKey struct { // keys necessary to decrypt it when delegated. type EncryptedData struct { Version int - VaultId int - Labels []string - KeySet []MultiWrappedKey - KeySetRSA map[string]SingleWrappedKey - IV []byte + VaultId int `json:",omitempty"` + Labels []string `json:",omitempty"` + KeySet []MultiWrappedKey `json:",omitempty"` + KeySetRSA map[string]SingleWrappedKey `json:",omitempty"` + IV []byte `json:",omitempty"` Data []byte Signature []byte } -// encryptKey encrypts data with the key associated with name inner, -// then name outer -func encryptKey(nameInner, nameOuter string, clearKey []byte, pubKeys map[string]SingleWrappedKey) (out MultiWrappedKey, err error) { - out.Name = []string{nameOuter, nameInner} - - recInner, ok := passvault.GetRecord(nameInner) - if !ok { - err = errors.New("Missing user on disk") - return - } - - recOuter, ok := passvault.GetRecord(nameOuter) - if !ok { - err = errors.New("Missing user on disk") - return - } - - if recInner.Type != recOuter.Type { - err = errors.New("Mismatched record types") - return - } - - var keyBytes []byte - var overrideInner SingleWrappedKey - var overrideOuter SingleWrappedKey - - // For AES records, use the live user key - // For RSA and ECC records, use the public key from the passvault - switch recInner.Type { - case passvault.RSARecord, passvault.ECCRecord: - if overrideInner, ok = pubKeys[nameInner]; !ok { - err = errors.New("Missing user in file") - return - } - - if overrideOuter, ok = pubKeys[nameOuter]; !ok { - err = errors.New("Missing user in file") - return - } - case passvault.AESRecord: - break - - default: - return out, errors.New("Unknown record type inner") - } - - // double-wrap the keys - if keyBytes, err = keycache.EncryptKey(clearKey, nameInner, overrideInner.aesKey); err != nil { - return out, err - } - if keyBytes, err = keycache.EncryptKey(keyBytes, nameOuter, overrideOuter.aesKey); err != nil { - return out, err - } - - out.Key = keyBytes - - return +type pair struct { + name string + key []byte } -// unwrapKey decrypts first key in keys whose encryption keys are in keycache -func unwrapKey(keys []MultiWrappedKey, pubKeys map[string]SingleWrappedKey, user string, labels []string) (unwrappedKey []byte, names []string, err error) { - var ( - keyFound error - fullMatch bool = false - nameSet = map[string]bool{} - ) +type mwkSlice []MultiWrappedKey +type swkSlice []pair - for _, mwKey := range keys { - if err != nil { - return nil, nil, err - } - - tmpKeyValue := mwKey.Key - - 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, user, labels, pubEncrypted.Key); keyFound != nil { - break - } - nameSet[mwName] = true - } - if keyFound == nil { - fullMatch = true - // concatenate all the decrypted bytes - unwrappedKey = tmpKeyValue - break - } - } - - if !fullMatch { - err = errors.New("Need more delegated keys") - names = nil - } - - names = make([]string, 0, len(nameSet)) - for name := range nameSet { - names = append(names, name) - } - return -} - -// mwkSorter describes a slice of MultiWrappedKeys to be sorted. -type mwkSorter struct { - keySet []MultiWrappedKey -} - -// Len is part of sort.Interface. -func (s *mwkSorter) Len() int { - return len(s.keySet) -} - -// Swap is part of sort.Interface. -func (s *mwkSorter) Swap(i, j int) { - s.keySet[i], s.keySet[j] = s.keySet[j], s.keySet[i] -} - -// Less is part of sort.Interface, it sorts lexicographically -// based on the list of names -func (s *mwkSorter) Less(i, j int) bool { +func (s mwkSlice) Len() int { return len(s) } +func (s mwkSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s mwkSlice) Less(i, j int) bool { // Alphabetic order var shorter = i - if len(s.keySet[i].Name) > len(s.keySet[j].Name) { + if len(s[i].Name) > len(s[j].Name) { shorter = j } - for index := range s.keySet[shorter].Name { - if s.keySet[i].Name[index] != s.keySet[j].Name[index] { - return s.keySet[i].Name[index] < s.keySet[j].Name[index] + + for index := range s[shorter].Name { + if s[i].Name[index] != s[j].Name[index] { + return s[i].Name[index] < s[j].Name[index] } } return false } -// swkSorter joins a slice of names with SingleWrappedKeys to be sorted. -type pair struct { - name string - key []byte -} - -type swkSorter []pair - -// Len is part of sort.Interface. -func (s swkSorter) Len() int { - return len(s) -} - -// Swap is part of sort.Interface. -func (s swkSorter) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// Less is part of sort.Interface. -func (s swkSorter) Less(i, j int) bool { - return s[i].name < s[j].name -} +func (s swkSlice) Len() int { return len(s) } +func (s swkSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s swkSlice) Less(i, j int) bool { return s[i].name < s[j].name } // computeHmac computes the signature of the encrypted data structure // the signature takes into account every element of the EncryptedData // structure, with all keys sorted alphabetically by name -func computeHmac(key []byte, encrypted EncryptedData) []byte { +func (encrypted *EncryptedData) computeHmac(key []byte) []byte { mac := hmac.New(sha1.New, key) // sort the multi-wrapped keys - mwks := &mwkSorter{ - keySet: encrypted.KeySet, - } + mwks := mwkSlice(encrypted.KeySet) sort.Sort(mwks) // sort the singly-wrapped keys - var swks swkSorter + var swks swkSlice for name, val := range encrypted.KeySetRSA { swks = append(swks, pair{name, val.Key}) } @@ -261,97 +153,229 @@ func computeHmac(key []byte, encrypted EncryptedData) []byte { 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, labels, leftNames, rightNames []string, min int) (resp []byte, err error) { - if min > 2 { - return nil, errors.New("Minimum restricted to 2") - } - - var encrypted EncryptedData - encrypted.Version = DEFAULT_VERSION - if encrypted.VaultId, err = passvault.GetVaultId(); err != nil { - return - } - - // Generate random IV and encryption key - ivBytes, err := symcrypt.MakeRandom(16) +func (encrypted *EncryptedData) lock(key []byte) (err error) { + payload, err := json.Marshal(encrypted) if err != nil { return } - // append used here to make a new slice from ivBytes and assign to - // encrypted.IV + mac := hmac.New(sha1.New, key) + mac.Write(payload) + sig := mac.Sum(nil) - encrypted.IV = append([]byte{}, ivBytes...) - clearKey, err := symcrypt.MakeRandom(16) - if err != nil { + *encrypted = EncryptedData{ + Version: -1, + Data: payload, + Signature: sig, + } + + return +} + +func (encrypted *EncryptedData) unlock(key []byte) (err error) { + if encrypted.Version != -1 { return } - var names = make(map[string]bool) - var overlap int + mac := hmac.New(sha1.New, key) + mac.Write(encrypted.Data) + sig := mac.Sum(nil) - // Count overlapping names, we don't want to double-encrypt - // with the same name - for _, n := range leftNames { - names[n] = true - } - for _, n := range rightNames { - used, ok := names[n] - if !used && ok { - names[n] = true - } else { - overlap++ - } + if !hmac.Equal(encrypted.Signature, sig) { + err = errors.New("Signature mismatch") + return } - // Allocate set of keys to be able to cover all unequal pairs of - // names with one from leftNames and one from rightNames + return json.Unmarshal(encrypted.Data, encrypted) +} - // Combinatorially, the number of ordered pairs with one element - // from one set and one from another for which both elements of - // the pair is distinct is - // len(n) * len(k) - overlap - encrypted.KeySet = make([]MultiWrappedKey, (len(leftNames)*len(rightNames) - overlap)) - encrypted.KeySetRSA = make(map[string]SingleWrappedKey) - - var singleWrappedKey SingleWrappedKey - for name := range names { - rec, ok := passvault.GetRecord(name) +// wrapKey encrypts the clear key such that a minimum number of delegated keys +// are required to decrypt. NOTE: Currently the max value for min is 2. +func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []byte, access AccessStructure) (err error) { + generateRandomKey := func(name string) (singleWrappedKey SingleWrappedKey, err error) { + rec, ok := records.GetRecord(name) if !ok { err = errors.New("Missing user on disk") return } - if rec.GetType() == passvault.RSARecord || rec.GetType() == passvault.ECCRecord { - // only wrap key with RSA key if found - if singleWrappedKey.aesKey, err = symcrypt.MakeRandom(16); err != nil { - return nil, err - } + if singleWrappedKey.aesKey, err = symcrypt.MakeRandom(16); err != nil { + return + } - if singleWrappedKey.Key, err = rec.EncryptKey(singleWrappedKey.aesKey); err != nil { - return nil, err + if singleWrappedKey.Key, err = rec.EncryptKey(singleWrappedKey.aesKey); err != nil { + return + } + + return + } + + encryptKey := func(outer, inner string, clearKey []byte) (keyBytes []byte, err error) { + var outerCrypt, innerCrypt cipher.Block + keyBytes = make([]byte, 16) + + outerCrypt, err = aes.NewCipher(encrypted.KeySetRSA[outer].aesKey) + if err != nil { + return + } + + innerCrypt, err = aes.NewCipher(encrypted.KeySetRSA[inner].aesKey) + if err != nil { + return + } + + innerCrypt.Encrypt(keyBytes, clearKey) + outerCrypt.Encrypt(keyBytes, keyBytes) + + return + } + + if len(access.Names) > 0 { + // Generate a random AES key for each user and RSA/ECIES encrypt it + encrypted.KeySetRSA = make(map[string]SingleWrappedKey) + + for _, name := range access.Names { + encrypted.KeySetRSA[name], err = generateRandomKey(name) + if err != nil { + return err } - encrypted.KeySetRSA[name] = singleWrappedKey - } else { - err = nil + } + + // 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 err != nil { + return err + } + + out := MultiWrappedKey{ + Name: []string{access.Names[i], access.Names[j]}, + Key: keyBytes, + } + + encrypted.KeySet = append(encrypted.KeySet, out) + } + } + + return nil + } 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) + + for _, name := range access.LeftNames { + encrypted.KeySetRSA[name], err = generateRandomKey(name) + if err != nil { + return err + } + } + + for _, name := range access.RightNames { + encrypted.KeySetRSA[name], err = generateRandomKey(name) + if err != nil { + return err + } + } + + // encrypt file key with every combination of one left key and one right key + encrypted.KeySet = make([]MultiWrappedKey, 0) + + for _, leftName := range access.LeftNames { + for _, rightName := range access.RightNames { + keyBytes, err := encryptKey(leftName, rightName, clearKey) + if err != nil { + return err + } + + out := MultiWrappedKey{ + Name: []string{leftName, rightName}, + Key: keyBytes, + } + + encrypted.KeySet = append(encrypted.KeySet, out) + } + } + + return nil + } else { + return errors.New("Invalid access structure.") + } +} + +// unwrapKey decrypts first key in keys whose encryption keys are in keycache +func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (unwrappedKey []byte, names []string, err error) { + var ( + keyFound error + fullMatch bool = false + nameSet = map[string]bool{} + ) + + for _, mwKey := range encrypted.KeySet { + // validate the size of the keys + if len(mwKey.Key) != 16 { + err = errors.New("Invalid Input") + } + + if err != nil { + return nil, nil, err + } + + tmpKeyValue := mwKey.Key + + for _, mwName := range mwKey.Name { + pubEncrypted := encrypted.KeySetRSA[mwName] + // if this is null, it's an AES encrypted key + if tmpKeyValue, keyFound = cache.DecryptKey(tmpKeyValue, mwName, user, encrypted.Labels, pubEncrypted.Key); keyFound != nil { + break + } + nameSet[mwName] = true + } + if keyFound == nil { + fullMatch = true + // concatenate all the decrypted bytes + unwrappedKey = tmpKeyValue + break } } - // encrypt file key with every combination of two keys - var n int - for _, nameOuter := range leftNames { - for _, nameInner := range rightNames { - if nameInner != nameOuter { - encrypted.KeySet[n], err = encryptKey(nameInner, nameOuter, clearKey, encrypted.KeySetRSA) - n += 1 - } - if err != nil { - return - } - } + if !fullMatch { + err = errors.New("Need more delegated keys") + names = nil + } + + names = make([]string, 0, len(nameSet)) + for name := range nameSet { + names = append(names, name) + } + return +} + +// 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 (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (resp []byte, err error) { + var encrypted EncryptedData + encrypted.Version = DEFAULT_VERSION + if encrypted.VaultId, err = c.records.GetVaultID(); err != nil { + return + } + + // Generate random IV and encryption key + encrypted.IV, err = symcrypt.MakeRandom(16) + if err != nil { + return + } + + clearKey, err := symcrypt.MakeRandom(16) + if err != nil { + return + } + + err = encrypted.wrapKey(c.records, clearKey, access) + if err != nil { + return } // encrypt file with clear key @@ -363,55 +387,55 @@ func Encrypt(in []byte, labels, leftNames, rightNames []string, min int) (resp [ clearFile := padding.AddPadding(in) encryptedFile := make([]byte, len(clearFile)) - aesCBC := cipher.NewCBCEncrypter(aesCrypt, ivBytes) + aesCBC := cipher.NewCBCEncrypter(aesCrypt, encrypted.IV) aesCBC.CryptBlocks(encryptedFile, clearFile) encrypted.Data = encryptedFile encrypted.Labels = labels - hmacKey, err := passvault.GetHmacKey() + hmacKey, err := c.records.GetHMACKey() if err != nil { return } - encrypted.Signature = computeHmac(hmacKey, encrypted) + encrypted.Signature = encrypted.computeHmac(hmacKey) + encrypted.lock(hmacKey) return json.Marshal(encrypted) } // Decrypt decrypts a file using the keys in the key cache. -func Decrypt(in []byte, user string) (resp []byte, names []string, err error) { +func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string, secure bool, err error) { // unwrap encrypted file var encrypted EncryptedData if err = json.Unmarshal(in, &encrypted); err != nil { return } - if encrypted.Version != DEFAULT_VERSION { - return nil, nil, errors.New("Unknown version") + if encrypted.Version != DEFAULT_VERSION && encrypted.Version != -1 { + return nil, nil, secure, errors.New("Unknown version") + } + + secure = encrypted.Version == -1 + + hmacKey, err := c.records.GetHMACKey() + if err != nil { + return + } + + if err = encrypted.unlock(hmacKey); err != nil { + return } // make sure file was encrypted with the active vault - vaultId, err := passvault.GetVaultId() + vaultId, err := c.records.GetVaultID() if err != nil { return } if encrypted.VaultId != vaultId { - return nil, nil, errors.New("Wrong vault") - } - - // validate the size of the keys - for _, multiKey := range encrypted.KeySet { - if len(multiKey.Key) != 16 { - err = errors.New("Invalid Input") - return - } + return nil, nil, secure, errors.New("Wrong vault") } // compute HMAC - hmacKey, err := passvault.GetHmacKey() - if err != nil { - return - } - expectedMAC := computeHmac(hmacKey, encrypted) + expectedMAC := encrypted.computeHmac(hmacKey) if !hmac.Equal(encrypted.Signature, expectedMAC) { err = errors.New("Signature mismatch") return @@ -419,7 +443,7 @@ func Decrypt(in []byte, user string) (resp []byte, names []string, err error) { // decrypt file key with delegate keys var unwrappedKey = make([]byte, 16) - unwrappedKey, names, err = unwrapKey(encrypted.KeySet, encrypted.KeySetRSA, user, encrypted.Labels) + unwrappedKey, names, err = encrypted.unwrapKey(c.cache, user) if err != nil { return } diff --git a/cryptor/cryptor_test.go b/cryptor/cryptor_test.go index d3d2d3a..011c036 100644 --- a/cryptor/cryptor_test.go +++ b/cryptor/cryptor_test.go @@ -22,7 +22,7 @@ func TestHash(t *testing.T) { var hmacKey, _ = base64.StdEncoding.DecodeString("Qugc5ZQ0vC7KQSgmDHTVgQ==") var signature = append([]byte{}, encrypted.Signature...) - expectedSig := computeHmac(hmacKey, encrypted) + expectedSig := encrypted.computeHmac(hmacKey) if diff := bytes.Compare(signature, expectedSig); diff != 0 { t.Fatalf("Error comparing signature %v", base64.StdEncoding.EncodeToString(expectedSig)) @@ -30,7 +30,7 @@ func TestHash(t *testing.T) { // change version and check hmac encrypted.Version = 2 - unexpectedSig := computeHmac(hmacKey, encrypted) + unexpectedSig := encrypted.computeHmac(hmacKey) if diff := bytes.Compare(signature, unexpectedSig); diff == 0 { t.Fatalf("Error comparing signature") @@ -39,7 +39,7 @@ func TestHash(t *testing.T) { // change vaultid and check hmac encrypted.VaultId = 529853896 - unexpectedSig = computeHmac(hmacKey, encrypted) + unexpectedSig = encrypted.computeHmac(hmacKey) if diff := bytes.Compare(signature, unexpectedSig); diff == 0 { t.Fatalf("Error comparing signature") @@ -48,7 +48,7 @@ func TestHash(t *testing.T) { // swap two records and check hmac encrypted.KeySet[0], encrypted.KeySet[1] = encrypted.KeySet[1], encrypted.KeySet[0] - unexpectedSig = computeHmac(hmacKey, encrypted) + unexpectedSig = encrypted.computeHmac(hmacKey) if diff := bytes.Compare(signature, unexpectedSig); diff != 0 { t.Fatalf("Error comparing signature %v, %v", @@ -59,7 +59,7 @@ func TestHash(t *testing.T) { // delete RSA key and check hmac encrypted.Version = 1 delete(encrypted.KeySetRSA, "Carol") - unexpectedSig = computeHmac(hmacKey, encrypted) + unexpectedSig = encrypted.computeHmac(hmacKey) if diff := bytes.Compare(signature, unexpectedSig); diff == 0 { t.Fatalf("Error comparing signature") diff --git a/index.html b/index.html index c3812be..20a7889 100644 --- a/index.html +++ b/index.html @@ -385,10 +385,12 @@ data.Users = data.Users.split(','); for(var i=0, l=data.Users.length; iSuccessfully decrypted data:

'+ window.atob(d.Data)+'

Delegates: '+d.Delegates.sort().join(', ')+'

' }) ); + $form.find('.feedback').empty().append( makeAlert({ type: (d.Secure ? 'success' : 'warning'), message: '

Successfully decrypted data:

'+ window.atob(d.Data)+'

Delegates: '+d.Delegates.sort().join(', ')+'

' }) ); } }); }); diff --git a/keycache/keycache.go b/keycache/keycache.go index 95d0007..2d1886a 100644 --- a/keycache/keycache.go +++ b/keycache/keycache.go @@ -19,9 +19,6 @@ import ( "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 @@ -36,29 +33,12 @@ type ActiveUser struct { Admin bool Type string - aesKey []byte rsaKey rsa.PrivateKey eccKey *ecdsa.PrivateKey } -// matchUser returns the matching active user if present -// and a boolean to indicate its presence. -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 -} - -// setUser takes an ActiveUser and adds it to the cache. -func setUser(in ActiveUser, name string) { - UserKeys[name] = in +type Cache struct { + UserKeys map[string]ActiveUser // Decrypted keys in memory, indexed by name. } // matchesLabel returns true if this usage applies the user and label @@ -68,7 +48,7 @@ func (usage Usage) matchesLabel(labels []string) bool { if len(labels) == 0 { return true } - // + for _, validLabel := range usage.Labels { for _, label := range labels { if label == validLabel { @@ -97,42 +77,66 @@ func (usage Usage) matches(user string, labels []string) bool { return false } +func NewCache() Cache { + return Cache{make(map[string]ActiveUser)} +} + +// setUser takes an ActiveUser and adds it to the cache. +func (cache *Cache) setUser(in ActiveUser, name string) { + cache.UserKeys[name] = in +} + +// matchUser returns the matching active user if present +// and a boolean to indicate its presence. +func (cache *Cache) matchUser(name, user string, labels []string) (out ActiveUser, present bool) { + key, present := cache.UserKeys[name] + if present { + if key.Usage.matches(user, labels) { + return key, true + } else { + present = false + } + } + + return +} + // useKey decrements the counter on an active key // for decryption or symmetric encryption -func useKey(name, user string, labels []string) { - if val, present := matchUser(name, user, labels); present { +func (cache *Cache) useKey(name, user string, labels []string) { + if val, present := cache.matchUser(name, user, labels); present { val.Usage.Uses -= 1 - setUser(val, name) + cache.setUser(val, name) } } // GetSummary returns the list of active user keys. -func GetSummary() map[string]ActiveUser { - return UserKeys +func (cache *Cache) GetSummary() map[string]ActiveUser { + return cache.UserKeys } // FlushCache removes all delegated keys. -func FlushCache() { - for name := range UserKeys { - delete(UserKeys, name) +func (cache *Cache) FlushCache() { + for name := range cache.UserKeys { + delete(cache.UserKeys, name) } } // Refresh purges all expired or used up keys. -func Refresh() { - for name, active := range UserKeys { +func (cache *Cache) Refresh() { + for name, active := range cache.UserKeys { 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) + delete(cache.UserKeys, name) } } } // AddKeyFromRecord decrypts a key for a given record and adds it to the cache. -func AddKeyFromRecord(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, durationString string) (err error) { +func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, durationString string) (err error) { var current ActiveUser - Refresh() + cache.Refresh() // compute exipiration duration, err := time.ParseDuration(durationString) @@ -146,8 +150,6 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name, password string, us // get decryption keys switch record.Type { - case passvault.AESRecord: - current.aesKey, err = record.GetKeyAES(password) case passvault.RSARecord: current.rsaKey, err = record.GetKeyRSA(password) case passvault.ECCRecord: @@ -165,58 +167,19 @@ func AddKeyFromRecord(record passvault.PasswordRecord, name, password string, us current.Admin = record.Admin // add current to map (overwriting previous for this name) - setUser(current, name) - - return -} - -// EncryptKey encrypts a 16 byte key using the cached key corresponding to name. -// For AES keys, use the cached key. -// For RSA and EC keys, the cache is not necessary; use the override -// key instead. -func EncryptKey(in []byte, name string, override []byte) (out []byte, err error) { - Refresh() - - aesKey := override - - // if the override key is not set, extract from the cache - if aesKey == nil { - encryptKey, ok := matchUser(name, name, []string{}) - if !ok { - return nil, errors.New("Key not delegated") - } - - switch encryptKey.Type { - case passvault.AESRecord: - aesKey = encryptKey.aesKey - - default: - return out, errors.New("Require override for key") - } - - useKey(name, name, []string{}) - } - - // encrypt - aesSession, err := aes.NewCipher(aesKey) - if err != nil { - return - } - out = make([]byte, 16) - aesSession.Encrypt(out, in) + cache.setUser(current, name) return } // DecryptKey decrypts a 16 byte key using the key corresponding to the name parameter -// for AES keys, the cached AES key is used directly to decrypt in -// for RSA and EC keys, the cached RSA/EC key is used to decrypt +// 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, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) { - Refresh() +func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) { + cache.Refresh() - decryptKey, ok := matchUser(name, user, labels) + decryptKey, ok := cache.matchUser(name, user, labels) if !ok { return nil, errors.New("Key not delegated") } @@ -225,9 +188,6 @@ func DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey [ // pick the aesKey to use for decryption switch decryptKey.Type { - case passvault.AESRecord: - aesKey = decryptKey.aesKey - case passvault.RSARecord: // extract the aes key from the pubEncryptedKey aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, pubEncryptedKey, nil) @@ -253,7 +213,7 @@ func DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey [ out = make([]byte, 16) aesSession.Decrypt(out, in) - useKey(name, user, labels) + cache.useKey(name, user, labels) return } diff --git a/keycache/keycache_test.go b/keycache/keycache_test.go index 1a9ad7c..e70f640 100644 --- a/keycache/keycache_test.go +++ b/keycache/keycache_test.go @@ -4,230 +4,290 @@ package keycache import ( + "bytes" "github.com/cloudflare/redoctober/passvault" + "github.com/cloudflare/redoctober/symcrypt" "testing" "time" ) -var now = time.Now() -var nextYear = now.AddDate(1, 0, 0) -var emptyKey = make([]byte, 16) -var dummy = make([]byte, 16) - func TestUsesFlush(t *testing.T) { - singleUse := ActiveUser{ - Admin: true, - Type: passvault.AESRecord, - Usage: Usage{ - Expiry: nextYear, - Uses: 2, - }, - aesKey: emptyKey, + // Initialize passvault with one dummy user. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + // Initialize keycache and delegate the user's key to it. + cache := NewCache() + + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 2, "1h") + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.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 %v", UserKeys) + // Generate a random symmetric key, encrypt a blank block with it, and encrypt + // the key itself with the user's public key. + dummy := make([]byte, 16) + key, err := symcrypt.MakeRandom(16) + if err != nil { + t.Fatalf("%v", err) } - DecryptKey(dummy, "first", "", []string{}, nil) + encKey, err := symcrypt.EncryptCBC(dummy, dummy, key) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 0 { - t.Fatalf("Error in number of live keys %v", UserKeys) + pubEncryptedKey, err := pr.EncryptKey(key) + if err != nil { + t.Fatalf("%v", err) + } + + key2, err := cache.DecryptKey(encKey, "user", "anybody", []string{}, pubEncryptedKey) + if err != nil { + t.Fatalf("%v", err) + } + + if bytes.Equal(key, key2) { + t.Fatalf("cache.DecryptKey didnt decrypt the right key!") + } + + // Second decryption allowed. + _, err = cache.DecryptKey(encKey, "user", "anybody else", []string{}, pubEncryptedKey) + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.UserKeys) != 0 { + t.Fatalf("Error in number of live keys %v", cache.UserKeys) } } func TestTimeFlush(t *testing.T) { - oneSec, _ := time.ParseDuration("1s") - one := now.Add(oneSec) - - singleUse := ActiveUser{ - Admin: true, - Type: passvault.AESRecord, - Usage: Usage{ - Expiry: one, - Uses: 10, - }, - aesKey: emptyKey, + // Initialize passvault and keycache. Delegate a key for 1s, wait a + // second and then make sure that it's gone. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + cache := NewCache() + + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 10, "1s") + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.UserKeys) != 1 { t.Fatalf("Error in number of live keys") } - EncryptKey(dummy, "first", nil) + time.Sleep(time.Second) - Refresh() - if len(UserKeys) != 1 { - t.Fatalf("Error in number of live keys") + dummy := make([]byte, 16) + pubEncryptedKey, err := pr.EncryptKey(dummy) + if err != nil { + t.Fatalf("%v", err) } - time.Sleep(oneSec) - - _, err := DecryptKey(dummy, "first", "", []string{}, nil) - + _, err = cache.DecryptKey(dummy, "user", "anybody", []string{}, pubEncryptedKey) 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, + // Initialize passvault and keycache. Delegate a key with the tag "red" and + // verify that decryption with the tag "red" is allowed. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + cache := NewCache() + + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "1h") + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.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") + dummy := make([]byte, 16) + pubEncryptedKey, err := pr.EncryptKey(dummy) + if err != nil { + t.Fatalf("%v", err) } - DecryptKey(dummy, "first", "", []string{"red"}, nil) + _, err = cache.DecryptKey(dummy, "user", "anybody", []string{"red"}, pubEncryptedKey) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 0 { - t.Fatalf("Error in number of live keys %v", UserKeys) + cache.Refresh() + if len(cache.UserKeys) != 0 { + t.Fatalf("Error in number of live keys %v", cache.UserKeys) } } func TestBadLabel(t *testing.T) { - singleUse := ActiveUser{ - Admin: true, - Type: passvault.AESRecord, - Usage: Usage{ - Expiry: nextYear, - Uses: 2, - Labels: []string{"red"}, - }, - aesKey: emptyKey, + // Initialize passvault and keycache. Delegate a key with the tag "red" and + // verify that decryption with the tag "blue" is disallowed. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + cache := NewCache() + + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "1h") + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.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") + dummy := make([]byte, 16) + pubEncryptedKey, err := pr.EncryptKey(dummy) + if err != nil { + t.Fatalf("%v", err) } - _, err := DecryptKey(dummy, "first", "", []string{"blue"}, nil) - + _, err = cache.DecryptKey(dummy, "user", "anybody", []string{"blue"}, pubEncryptedKey) if err == nil { - t.Fatalf("Decryption of labeled key with no permission") + t.Fatalf("Decryption of labeled key allowed without permission.") } - Refresh() - if len(UserKeys) != 1 { - t.Fatalf("Error in number of live keys %v", UserKeys) + cache.Refresh() + if len(cache.UserKeys) != 1 { + t.Fatalf("Error in number of live keys %v", cache.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, + // Initialize passvault and keycache. Delegate a key with tag and user + // restrictions and verify that permissible decryption is allowed. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + cache := NewCache() + + err = cache.AddKeyFromRecord( + pr, "user", "weakpassword", + []string{"ci", "buildeng", "user"}, + []string{"red", "blue"}, + 1, "1h", + ) + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.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") + dummy := make([]byte, 16) + pubEncryptedKey, err := pr.EncryptKey(dummy) + if err != nil { + t.Fatalf("%v", err) } - DecryptKey(dummy, "first", "ci", []string{"red"}, nil) + _, err = cache.DecryptKey(dummy, "user", "ci", []string{"red"}, pubEncryptedKey) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 0 { - t.Fatalf("Error in number of live keys %v", UserKeys) + cache.Refresh() + if len(cache.UserKeys) != 0 { + t.Fatalf("Error in number of live keys %v", cache.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, + // Initialize passvault and keycache. Delegate a key with tag and user + // restrictions and verify that illegal decryption is disallowed. + records, err := passvault.InitFrom("memory") + if err != nil { + t.Fatalf("%v", err) } - UserKeys["first"] = singleUse + pr, err := records.AddNewRecord("user", "weakpassword", true, passvault.DefaultRecordType) + if err != nil { + t.Fatalf("%v", err) + } - Refresh() - if len(UserKeys) != 1 { + cache := NewCache() + + err = cache.AddKeyFromRecord( + pr, "user", "weakpassword", + []string{"ci", "buildeng", "user"}, + []string{"red", "blue"}, + 1, "1h", + ) + if err != nil { + t.Fatalf("%v", err) + } + + cache.Refresh() + if len(cache.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") + dummy := make([]byte, 16) + pubEncryptedKey, err := pr.EncryptKey(dummy) + if err != nil { + t.Fatalf("%v", err) } - _, err := DecryptKey(dummy, "first", "", []string{"blue"}, nil) - + _, err = cache.DecryptKey(dummy, "user", "anybody", []string{"blue"}, pubEncryptedKey) if err == nil { - t.Fatalf("Decryption of labeled key by unauthorized user") + t.Fatalf("Decryption of labeled key allowed without permission.") } - Refresh() - if len(UserKeys) != 1 { - t.Fatalf("Error in number of live keys %v", UserKeys) + cache.Refresh() + if len(cache.UserKeys) != 1 { + t.Fatalf("Error in number of live keys %v", cache.UserKeys) } } diff --git a/passvault/passvault.go b/passvault/passvault.go index 21ff838..d81df4d 100644 --- a/passvault/passvault.go +++ b/passvault/passvault.go @@ -30,7 +30,6 @@ import ( // Constants for record type const ( - AESRecord = "AES" RSARecord = "RSA" ECCRecord = "ECC" ) @@ -47,9 +46,6 @@ const ( DEFAULT_VERSION = 1 ) -// Path of current vault -var localPath string - type ECPublicKey struct { Curve *elliptic.CurveParams X, Y *big.Int @@ -74,7 +70,6 @@ type PasswordRecord struct { PasswordSalt []byte HashedPassword []byte KeySalt []byte - AESKey []byte RSAKey struct { RSAExp []byte RSAExpIV []byte @@ -94,16 +89,14 @@ type PasswordRecord struct { // diskRecords is the structure used to read and write a JSON file // containing the contents of a password vault -type diskRecords struct { +type Records struct { Version int VaultId int HmacKey []byte Passwords map[string]PasswordRecord -} -// records is the set of encrypted records read from disk and -// unmarshalled -var records diskRecords + localPath string // Path of current vault +} // Summary is a minmial account summary. type Summary struct { @@ -173,8 +166,8 @@ func encryptECCRecord(newRec *PasswordRecord, ecPriv *ecdsa.PrivateKey, passKey } // createPasswordRec creates a new record from a username and password -func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err error) { - newRec.Type = DefaultRecordType +func createPasswordRec(password string, admin bool, userType string) (newRec PasswordRecord, err error) { + newRec.Type = userType if newRec.PasswordSalt, err = symcrypt.MakeRandom(16); err != nil { return @@ -194,7 +187,7 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err } // generate a key pair - switch DefaultRecordType { + switch userType { case RSARecord: var rsaPriv *rsa.PrivateKey rsaPriv, err = rsa.GenerateKey(rand.Reader, 2048) @@ -219,16 +212,8 @@ func createPasswordRec(password string, admin bool) (newRec PasswordRecord, err newRec.ECKey.ECPublic.Curve = ecPriv.PublicKey.Curve.Params() newRec.ECKey.ECPublic.X = ecPriv.PublicKey.X newRec.ECKey.ECPublic.Y = ecPriv.PublicKey.Y - } - - // encrypt AES key with password key - aesKey, err := symcrypt.MakeRandom(16) - if err != nil { - return - } - - if newRec.AESKey, err = encryptECB(aesKey, passKey); err != nil { - return + default: + err = errors.New("Unknown record type") } newRec.Admin = admin @@ -269,14 +254,18 @@ func encryptECB(data, key []byte) (encryptedData []byte, err error) { } // InitFromDisk reads the record from disk and initialize global context. -func InitFromDisk(path string) error { - jsonDiskRecord, err := ioutil.ReadFile(path) +func InitFrom(path string) (records Records, err error) { + var jsonDiskRecord []byte - // It's OK for the file to be missing, we'll create it later if - // anything is added. + if path != "memory" { + jsonDiskRecord, err = ioutil.ReadFile(path) - if err != nil && !os.IsNotExist(err) { - return err + // It's OK for the file to be missing, we'll create it later if + // anything is added. + + if err != nil && !os.IsNotExist(err) { + return + } } // Initialized so that we can determine later if anything was read @@ -286,49 +275,47 @@ func InitFromDisk(path string) error { if len(jsonDiskRecord) != 0 { if err = json.Unmarshal(jsonDiskRecord, &records); err != nil { - return err + return } } - formatErr := errors.New("Format error") + err = errors.New("Format error") for _, rec := range records.Passwords { if len(rec.PasswordSalt) != 16 { - return formatErr + return } if len(rec.HashedPassword) != 16 { - return formatErr + return } if len(rec.KeySalt) != 16 { - return formatErr - } - if rec.Type == AESRecord { - if len(rec.AESKey) != 16 { - return formatErr - } + return } if rec.Type == RSARecord { if len(rec.RSAKey.RSAExp) == 0 || len(rec.RSAKey.RSAExp)%16 != 0 { - return formatErr + return } if len(rec.RSAKey.RSAPrimeP) == 0 || len(rec.RSAKey.RSAPrimeP)%16 != 0 { - return formatErr + return } if len(rec.RSAKey.RSAPrimeQ) == 0 || len(rec.RSAKey.RSAPrimeQ)%16 != 0 { - return formatErr + return } if len(rec.RSAKey.RSAExpIV) != 16 { - return formatErr + return } if len(rec.RSAKey.RSAPrimePIV) != 16 { - return formatErr + return } if len(rec.RSAKey.RSAPrimeQIV) != 16 { - return formatErr + return } } if rec.Type == ECCRecord { - if len(rec.ECKey.ECPriv) == 0 { - return formatErr + if len(rec.ECKey.ECPriv) == 0 || len(rec.ECKey.ECPriv)%16 != 0 { + return + } + if len(rec.ECKey.ECPrivIV) != 16 { + return } } } @@ -341,69 +328,82 @@ func InitFromDisk(path string) error { records.VaultId = int(mrand.Int31()) records.HmacKey, err = symcrypt.MakeRandom(16) if err != nil { - return err + return } records.Passwords = make(map[string]PasswordRecord) } - localPath = path + records.localPath = path - return nil + err = nil + return } // WriteRecordsToDisk saves the current state of the records to disk. -func WriteRecordsToDisk() error { - if !IsInitialized() { - return errors.New("Path not initialized") +func (records *Records) WriteRecordsToDisk() error { + if records.localPath == "memory" { + return nil } jsonDiskRecord, err := json.Marshal(records) if err != nil { return err } - return ioutil.WriteFile(localPath, jsonDiskRecord, 0644) + return ioutil.WriteFile(records.localPath, jsonDiskRecord, 0644) } // AddNewRecord adds a new record for a given username and password. -func AddNewRecord(name, password string, admin bool) (PasswordRecord, error) { - pr, err := createPasswordRec(password, admin) +func (records *Records) AddNewRecord(name, password string, admin bool, userType string) (PasswordRecord, error) { + pr, err := createPasswordRec(password, admin, userType) if err != nil { return pr, err } - SetRecord(pr, name) - return pr, WriteRecordsToDisk() + records.SetRecord(pr, name) + return pr, records.WriteRecordsToDisk() } // ChangePassword changes the password for a given user. -func ChangePassword(name, password, newPassword string) (err error) { - pr, ok := GetRecord(name) +func (records *Records) ChangePassword(name, password, newPassword string) (err error) { + pr, ok := records.GetRecord(name) if !ok { err = errors.New("Record not present") return } - if err = pr.ValidatePassword(password); err != nil { + + var keySalt []byte + if keySalt, err = symcrypt.MakeRandom(16); err != nil { + return + } + newPassKey, err := derivePasswordKey(newPassword, keySalt) + if err != nil { return } - // decrypt key - var key []byte - var rsaKey rsa.PrivateKey - var ecKey *ecdsa.PrivateKey - if pr.Type == AESRecord { - key, err = pr.GetKeyAES(password) - if err != nil { - return - } - } else if pr.Type == RSARecord { + // decrypt with old password and re-encrypt original key with new password + if pr.Type == RSARecord { + var rsaKey rsa.PrivateKey rsaKey, err = pr.GetKeyRSA(password) if err != nil { return } + + // encrypt RSA key with password key + err = encryptRSARecord(&pr, &rsaKey, newPassKey) + if err != nil { + return + } } else if pr.Type == ECCRecord { + var ecKey *ecdsa.PrivateKey ecKey, err = pr.GetKeyECC(password) if err != nil { return } + + // encrypt ECDSA key with password key + err = encryptECCRecord(&pr, ecKey, newPassKey) + if err != nil { + return + } } else { err = errors.New("Unkown record type") return @@ -417,115 +417,70 @@ func ChangePassword(name, password, newPassword string) (err error) { return } - if pr.KeySalt, err = symcrypt.MakeRandom(16); err != nil { - return - } - newPassKey, err := derivePasswordKey(newPassword, pr.KeySalt) - if err != nil { - return - } + pr.KeySalt = keySalt - // encrypt original key with new password - if pr.Type == AESRecord { - pr.AESKey, err = encryptECB(key, newPassKey) - if err != nil { - return - } - } else if pr.Type == RSARecord { - // encrypt RSA key with password key - err = encryptRSARecord(&pr, &rsaKey, newPassKey) - if err != nil { - return - } - } else if pr.Type == ECCRecord { - // encrypt ECDSA key with password key - err = encryptECCRecord(&pr, ecKey, newPassKey) - if err != nil { - return - } - } else { - err = errors.New("Unkown record type") - return - } + records.SetRecord(pr, name) - SetRecord(pr, name) - - return WriteRecordsToDisk() + return records.WriteRecordsToDisk() } // DeleteRecord deletes a given record. -func DeleteRecord(name string) error { - if _, ok := GetRecord(name); ok { +func (records *Records) DeleteRecord(name string) error { + if _, ok := records.GetRecord(name); ok { delete(records.Passwords, name) - return WriteRecordsToDisk() + return records.WriteRecordsToDisk() } - return errors.New("Record missing") } // RevokeRecord removes admin status from a record. -func RevokeRecord(name string) error { - if rec, ok := GetRecord(name); ok { +func (records *Records) RevokeRecord(name string) error { + if rec, ok := records.GetRecord(name); ok { rec.Admin = false - SetRecord(rec, name) - return WriteRecordsToDisk() + records.SetRecord(rec, name) + return records.WriteRecordsToDisk() } - return errors.New("Record missing") } // MakeAdmin adds admin status to a given record. -func MakeAdmin(name string) error { - if rec, ok := GetRecord(name); ok { +func (records *Records) MakeAdmin(name string) error { + if rec, ok := records.GetRecord(name); ok { rec.Admin = true - SetRecord(rec, name) - return WriteRecordsToDisk() + records.SetRecord(rec, name) + return records.WriteRecordsToDisk() } - return errors.New("Record missing") } // SetRecord puts a record into the global status. -func SetRecord(pr PasswordRecord, name string) { +func (records *Records) SetRecord(pr PasswordRecord, name string) { records.Passwords[name] = pr } // GetRecord returns a record given a name. -func GetRecord(name string) (PasswordRecord, bool) { +func (records *Records) GetRecord(name string) (PasswordRecord, bool) { dpr, found := records.Passwords[name] return dpr, found } // GetVaultId returns the id of the current vault. -func GetVaultId() (id int, err error) { - if !IsInitialized() { - return 0, errors.New("Path not initialized") - } - +func (records *Records) GetVaultID() (id int, err error) { return records.VaultId, nil } // GetHmacKey returns the hmac key of the current vault. -func GetHmacKey() (key []byte, err error) { - if !IsInitialized() { - return nil, errors.New("Path not initialized") - } - +func (records *Records) GetHMACKey() (key []byte, err error) { return records.HmacKey, nil } -// IsInitialized returns true if the disk vault has been loaded. -func IsInitialized() bool { - return localPath != "" -} - // NumRecords returns the number of records in the vault. -func NumRecords() int { +func (records *Records) NumRecords() int { return len(records.Passwords) } // GetSummary returns a summary of the records on disk. -func GetSummary() (summary map[string]Summary) { +func (records *Records) GetSummary() (summary map[string]Summary) { summary = make(map[string]Summary) for name, pass := range records.Passwords { summary[name] = Summary{pass.Admin, pass.Type} @@ -534,17 +489,17 @@ func GetSummary() (summary map[string]Summary) { } // IsAdmin returns the admin status of the PasswordRecord. -func (pr PasswordRecord) IsAdmin() bool { +func (pr *PasswordRecord) IsAdmin() bool { return pr.Admin } // GetType returns the type status of the PasswordRecord. -func (pr PasswordRecord) GetType() string { +func (pr *PasswordRecord) GetType() string { return pr.Type } // EncryptKey encrypts a 16-byte key with the RSA or EC key of the record. -func (pr PasswordRecord) EncryptKey(in []byte) (out []byte, err error) { +func (pr *PasswordRecord) EncryptKey(in []byte) (out []byte, err error) { if pr.Type == RSARecord { return rsa.EncryptOAEP(sha1.New(), rand.Reader, &pr.RSAKey.RSAPublic, in, nil) } else if pr.Type == ECCRecord { @@ -554,27 +509,8 @@ func (pr PasswordRecord) EncryptKey(in []byte) (out []byte, err error) { } } -// GetKeyAES returns the 16-byte key of the record. -func (pr PasswordRecord) GetKeyAES(password string) (key []byte, err error) { - if pr.Type != AESRecord { - return nil, errors.New("Invalid function for record type") - } - - err = pr.ValidatePassword(password) - if err != nil { - return - } - - passKey, err := derivePasswordKey(password, pr.KeySalt) - if err != nil { - return - } - - return decryptECB(pr.AESKey, passKey) -} - // GetKeyRSAPub returns the RSA public key of the record. -func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) { +func (pr *PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) { if pr.Type != RSARecord { return out, errors.New("Invalid function for record type") } @@ -582,7 +518,7 @@ func (pr PasswordRecord) GetKeyRSAPub() (out *rsa.PublicKey, err error) { } // GetKeyECCPub returns the ECDSA public key out of the record. -func (pr PasswordRecord) GetKeyECCPub() (out *ecdsa.PublicKey, err error) { +func (pr *PasswordRecord) GetKeyECCPub() (out *ecdsa.PublicKey, err error) { if pr.Type != ECCRecord { return out, errors.New("Invalid function for record type") } @@ -590,7 +526,7 @@ func (pr PasswordRecord) GetKeyECCPub() (out *ecdsa.PublicKey, err error) { } // GetKeyECC returns the ECDSA private key of the record given the correct password. -func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err error) { +func (pr *PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err error) { if pr.Type != ECCRecord { return key, errors.New("Invalid function for record type") } @@ -617,7 +553,7 @@ func (pr PasswordRecord) GetKeyECC(password string) (key *ecdsa.PrivateKey, err } // GetKeyRSA returns the RSA private key of the record given the correct password. -func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) { +func (pr *PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err error) { if pr.Type != RSARecord { return key, errors.New("Invalid function for record type") } @@ -674,14 +610,14 @@ func (pr PasswordRecord) GetKeyRSA(password string) (key rsa.PrivateKey, err err } // ValidatePassword returns an error if the password is incorrect. -func (pr PasswordRecord) ValidatePassword(password string) error { - if h, err := hashPassword(password, pr.PasswordSalt); err != nil { +func (pr *PasswordRecord) ValidatePassword(password string) error { + h, err := hashPassword(password, pr.PasswordSalt) + if err != nil { return err - } else { - if bytes.Compare(h, pr.HashedPassword) != 0 { - return errors.New("Wrong Password") - } } + if bytes.Compare(h, pr.HashedPassword) != 0 { + return errors.New("Wrong Password") + } return nil } diff --git a/passvault/passvault_test.go b/passvault/passvault_test.go index 51f54e0..7516520 100644 --- a/passvault/passvault_test.go +++ b/passvault/passvault_test.go @@ -5,33 +5,42 @@ package passvault import ( - "testing" "os" + "testing" ) func TestStaticVault(t *testing.T) { - err := InitFromDisk("/tmp/redoctober.json") + // Creates a temporary on-disk database to test if passvault can read and + // write from/to disk. It's deleted at the bottom of the function--this + // should be the only test that requires touching disk. + records, err := InitFrom("/tmp/redoctober.json") if err != nil { t.Fatalf("Error reading record") } - _, err = AddNewRecord("test", "bad pass", true) + _, err = records.AddNewRecord("test", "bad pass", true, DefaultRecordType) if err != nil { t.Fatalf("Error creating record") } - err = InitFromDisk("/tmp/redoctober.json") + + // Reads data written last time. + _, err = InitFrom("/tmp/redoctober.json") if err != nil { t.Fatalf("Error reading record") } + os.Remove("/tmp/redoctober.json") } func TestRSAEncryptDecrypt(t *testing.T) { - oldDefaultRecordType := DefaultRecordType - DefaultRecordType = RSARecord - myRec, err := createPasswordRec("mypasswordisweak", true) + records, err := InitFrom("memory") if err != nil { - t.Fatalf("Error creating record") + t.Fatalf("%v", err) + } + + myRec, err := records.AddNewRecord("user", "weakpassword", true, RSARecord) + if err != nil { + t.Fatalf("%v", err) } _, err = myRec.GetKeyRSAPub() @@ -44,7 +53,7 @@ func TestRSAEncryptDecrypt(t *testing.T) { t.Fatalf("Incorrect password did not fail") } - rsaPriv, err = myRec.GetKeyRSA("mypasswordisweak") + rsaPriv, err = myRec.GetKeyRSA("weakpassword") if err != nil { t.Fatalf("Error decrypting RSA key") } @@ -53,20 +62,22 @@ func TestRSAEncryptDecrypt(t *testing.T) { if err != nil { t.Fatalf("Error validating RSA key") } - DefaultRecordType = oldDefaultRecordType } func TestECCEncryptDecrypt(t *testing.T) { - oldDefaultRecordType := DefaultRecordType - DefaultRecordType = ECCRecord - myRec, err := createPasswordRec("mypasswordisweak", true) + records, err := InitFrom("memory") if err != nil { - t.Fatalf("Error creating record") + t.Fatalf("%v", err) + } + + myRec, err := records.AddNewRecord("user", "weakpassword", true, ECCRecord) + if err != nil { + t.Fatalf("%v", err) } _, err = myRec.GetKeyECCPub() if err != nil { - t.Fatalf("Error extracting EC pub") + t.Fatalf("%v", err) } _, err = myRec.GetKeyECC("mypasswordiswrong") @@ -74,9 +85,8 @@ func TestECCEncryptDecrypt(t *testing.T) { t.Fatalf("Incorrect password did not fail") } - _, err = myRec.GetKeyECC("mypasswordisweak") + _, err = myRec.GetKeyECC("weakpassword") if err != nil { - t.Fatalf("Error decrypting EC key") + t.Fatalf("%v", err) } - DefaultRecordType = oldDefaultRecordType } diff --git a/redoctober.go b/redoctober.go index 90ec880..e7e909b 100644 --- a/redoctober.go +++ b/redoctober.go @@ -89,7 +89,7 @@ func NewServer(process chan<- userRequest, staticPath, addr, certPath, keyPath, Rand: rand.Reader, PreferServerCipherSuites: true, SessionTicketsDisabled: true, - MinVersion: tls.VersionTLS10, + MinVersion: tls.VersionTLS10, } // If a caPath has been specified then a local CA is being used @@ -632,10 +632,12 @@ var indexHtml = []byte(` data.Users = data.Users.split(','); for(var i=0, l=data.Users.length; i data.Owners = data.Owners.split(','); for(var i=0, l=data.Owners.length; i data : data, success : function(d){ d = JSON.parse(window.atob(d.Response)); - $form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '

Successfully decrypted data:

'+ window.atob(d.Data)+'

Delegates: '+d.Delegates.sort().join(', ')+'

' }) ); + $form.find('.feedback').empty().append( makeAlert({ type: (d.Secure ? 'success' : 'warning'), message: '

Successfully decrypted data:

'+ window.atob(d.Data)+'

Delegates: '+d.Delegates.sort().join(', ')+'

' }) ); } }); });