Merge pull request #50 from Bren2010/patch05

Patches for Bugs & HMAC Malleability
This commit is contained in:
Kyle Isom
2015-05-07 14:42:59 -07:00
10 changed files with 707 additions and 725 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -385,10 +385,12 @@
data.Users = data.Users.split(',');
for(var i=0, l=data.Users.length; i<l; i++){
data.Users[i] = data.Users[i].trim();
if (data.Users[i] == "") { data.Users.splice(i, 1); }
}
data.Labels = data.Labels.split(',');
for(var i=0, l=data.Labels.length; i<l; i++){
data.Labels[i] = data.Labels[i].trim();
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
}
submit( $form, {
@@ -454,10 +456,12 @@
data.Owners = data.Owners.split(',');
for(var i=0, l=data.Owners.length; i<l; i++){
data.Owners[i] = data.Owners[i].trim();
if (data.Owners[i] == "") { data.Owners.splice(i, 1); }
}
data.Labels = data.Labels.split(',');
for(var i=0, l=data.Labels.length; i<l; i++){
data.Labels[i] = data.Labels[i].trim();
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
}
// Convert data to base64.
@@ -481,7 +485,7 @@
data : data,
success : function(d){
d = JSON.parse(window.atob(d.Response));
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Successfully decrypted data:</p><pre>'+ window.atob(d.Data)+'</pre><p>Delegates: '+d.Delegates.sort().join(', ')+'</p>' }) );
$form.find('.feedback').empty().append( makeAlert({ type: (d.Secure ? 'success' : 'warning'), message: '<p>Successfully decrypted data:</p><pre>'+ window.atob(d.Data)+'</pre><p>Delegates: '+d.Delegates.sort().join(', ')+'</p>' }) );
}
});
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(`<!DOCTYPE html>
data.Users = data.Users.split(',');
for(var i=0, l=data.Users.length; i<l; i++){
data.Users[i] = data.Users[i].trim();
if (data.Users[i] == "") { data.Users.splice(i, 1); }
}
data.Labels = data.Labels.split(',');
for(var i=0, l=data.Labels.length; i<l; i++){
data.Labels[i] = data.Labels[i].trim();
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
}
submit( $form, {
@@ -701,10 +703,12 @@ var indexHtml = []byte(`<!DOCTYPE html>
data.Owners = data.Owners.split(',');
for(var i=0, l=data.Owners.length; i<l; i++){
data.Owners[i] = data.Owners[i].trim();
if (data.Owners[i] == "") { data.Owners.splice(i, 1); }
}
data.Labels = data.Labels.split(',');
for(var i=0, l=data.Labels.length; i<l; i++){
data.Labels[i] = data.Labels[i].trim();
if (data.Labels[i] == "") { data.Labels.splice(i, 1); }
}
// Convert data to base64.
@@ -728,7 +732,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
data : data,
success : function(d){
d = JSON.parse(window.atob(d.Response));
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Successfully decrypted data:</p><pre>'+ window.atob(d.Data)+'</pre><p>Delegates: '+d.Delegates.sort().join(', ')+'</p>' }) );
$form.find('.feedback').empty().append( makeAlert({ type: (d.Secure ? 'success' : 'warning'), message: '<p>Successfully decrypted data:</p><pre>'+ window.atob(d.Data)+'</pre><p>Delegates: '+d.Delegates.sort().join(', ')+'</p>' }) );
}
});
});