From becabb40e9b6e3cea15480256576627ccc2419ba Mon Sep 17 00:00:00 2001 From: Brendan Mc Date: Tue, 26 May 2015 21:06:08 -0700 Subject: [PATCH 01/14] Integration with Bren2010/MSP --- core/core.go | 2 + cryptor/cryptor.go | 162 ++++++++++++++++++++++++++++++++----------- keycache/keycache.go | 57 +++++++++++++-- 3 files changed, 178 insertions(+), 43 deletions(-) diff --git a/core/core.go b/core/core.go index fa487c2..e3fccb1 100644 --- a/core/core.go +++ b/core/core.go @@ -72,6 +72,7 @@ type EncryptRequest struct { Owners []string LeftOwners []string RightOwners []string + Predicate string Data []byte @@ -454,6 +455,7 @@ func Encrypt(jsonIn []byte) ([]byte, error) { Names: s.Owners, LeftNames: s.LeftOwners, RightNames: s.RightOwners, + Predicate: s.Predicate, } resp, err := crypt.Encrypt(s.Data, s.Labels, access) diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 830b188..65aa035 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -15,6 +15,7 @@ import ( "sort" "strconv" + "github.com/Bren2010/msp" "github.com/cloudflare/redoctober/keycache" "github.com/cloudflare/redoctober/padding" "github.com/cloudflare/redoctober/passvault" @@ -44,6 +45,39 @@ type AccessStructure struct { LeftNames []string RightNames []string + + Predicate string +} + +// Implements msp.UserDatabase +type UserDatabase struct { + records *passvault.Records + cache *keycache.Cache + + user string + labels []string + keySet map[string]SingleWrappedKey + shareSet map[string][][]byte +} + +func (u UserDatabase) ValidUser(name string) bool { + _, ok := u.records.GetRecord(name) + return ok +} + +func (u UserDatabase) CanGetShare(name string) bool { + _, _, ok := u.cache.MatchUser(name, u.user, u.labels) + return ok +} + +func (u UserDatabase) GetShare(name string) ([][]byte, error) { + return u.cache.DecryptShares( + u.shareSet[name], + name, + u.user, + u.labels, + u.keySet[name].Key, + ) } // MultiWrappedKey is a structure containing a 16-byte key encrypted @@ -67,8 +101,10 @@ type EncryptedData struct { Version int VaultId int `json:",omitempty"` Labels []string `json:",omitempty"` + Predicate string `json:",omitempty"` KeySet []MultiWrappedKey `json:",omitempty"` KeySetRSA map[string]SingleWrappedKey `json:",omitempty"` + ShareSet map[string][][]byte `json:",omitempty"` IV []byte `json:",omitempty"` Data []byte Signature []byte @@ -259,8 +295,6 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b 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) @@ -301,11 +335,41 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b encrypted.KeySet = append(encrypted.KeySet, out) } } + } else if len(access.Predicate) > 0 { + encrypted.KeySetRSA = make(map[string]SingleWrappedKey) - return nil + sss, err := msp.StringToMSP(access.Predicate) + if err != nil { + return err + } + + db := msp.UserDatabase(UserDatabase{records: records}) + shareSet, err := sss.DistributeShares(clearKey, msp.Modulus(127), &db) + if err != nil { + return err + } + + for name, _ := range shareSet { + encrypted.KeySetRSA[name], err = generateRandomKey(name) + crypt, err := aes.NewCipher(encrypted.KeySetRSA[name].aesKey) + if err != nil { + return err + } + + for i, _ := range shareSet[name] { + tmp := make([]byte, 16) + crypt.Encrypt(tmp, shareSet[name][i]) + shareSet[name][i] = tmp + } + } + + encrypted.ShareSet = shareSet + encrypted.Predicate = access.Predicate } else { return errors.New("Invalid access structure.") } + + return nil } // unwrapKey decrypts first key in keys whose encryption keys are in keycache @@ -316,50 +380,70 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u 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 len(encrypted.Predicate) == 0 { + 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 + } + + // loop through users to see if they are all delegated + fullMatch = true + for _, mwName := range mwKey.Name { + if valid := cache.Valid(mwName, user, encrypted.Labels); !valid { + fullMatch = false + break + } + nameSet[mwName] = true + } + + // if the keys are delegated, decrypt the mwKey with them + if fullMatch == true { + tmpKeyValue := mwKey.Key + for _, mwName := range mwKey.Name { + pubEncrypted := encrypted.KeySetRSA[mwName] + if tmpKeyValue, keyFound = cache.DecryptKey(tmpKeyValue, mwName, user, encrypted.Labels, pubEncrypted.Key); keyFound != nil { + break + } + } + 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 + } else { + var sss msp.MSP + sss, err = msp.StringToMSP(encrypted.Predicate) if err != nil { return nil, nil, err } - // loop through users to see if they are all delegated - fullMatch = true - for _, mwName := range mwKey.Name { - if valid := cache.Valid(mwName, user, encrypted.Labels); !valid { - fullMatch = false - break - } - nameSet[mwName] = true - } + db := msp.UserDatabase(UserDatabase{ + cache: cache, + user: user, + labels: encrypted.Labels, + keySet: encrypted.KeySetRSA, + shareSet: encrypted.ShareSet, + }) + unwrappedKey, err = sss.RecoverSecret(msp.Modulus(127), &db) + names = []string{"Shares"} - // if the keys are delegated, decrypt the mwKey with them - if fullMatch == true { - tmpKeyValue := mwKey.Key - for _, mwName := range mwKey.Name { - pubEncrypted := encrypted.KeySetRSA[mwName] - if tmpKeyValue, keyFound = cache.DecryptKey(tmpKeyValue, mwName, user, encrypted.Labels, pubEncrypted.Key); keyFound != nil { - break - } - } - unwrappedKey = tmpKeyValue - break - } + 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 diff --git a/keycache/keycache.go b/keycache/keycache.go index 2c1082f..4e6df4f 100644 --- a/keycache/keycache.go +++ b/keycache/keycache.go @@ -112,9 +112,9 @@ func (cache *Cache) Valid(name, user string, labels []string) (present bool) { return false } -// matchUser returns the matching active user if present +// MatchUser returns the matching active user if present // and a boolean to indicate its presence. -func (cache *Cache) matchUser(name, user string, labels []string) (ActiveUser, string, bool) { +func (cache *Cache) MatchUser(name, user string, labels []string) (ActiveUser, string, bool) { var key ActiveUser for d, key := range cache.UserKeys { if d.Name != name { @@ -131,7 +131,7 @@ func (cache *Cache) matchUser(name, user string, labels []string) (ActiveUser, s // useKey decrements the counter on an active key // for decryption or symmetric encryption func (cache *Cache) useKey(name, user, slot string, labels []string) { - if val, slot, present := cache.matchUser(name, user, labels); present { + if val, slot, present := cache.MatchUser(name, user, labels); present { val.Usage.Uses -= 1 cache.setUser(val, name, slot) } @@ -214,7 +214,7 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) { cache.Refresh() - decryptKey, slot, ok := cache.matchUser(name, user, labels) + decryptKey, slot, ok := cache.MatchUser(name, user, labels) if !ok { return nil, errors.New("Key not delegated") } @@ -252,3 +252,52 @@ func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pu return } + +// DecryptShares decrypts an array of 16 byte shares using the key corresponding +// to the name parameter. +func (cache *Cache) DecryptShares(in [][]byte, name, user string, labels []string, pubEncryptedKey []byte) (out [][]byte, err error) { + cache.Refresh() + + decryptKey, slot, ok := cache.MatchUser(name, user, labels) + if !ok { + return nil, errors.New("Key not delegated") + } + + var aesKey []byte + + // pick the aesKey to use for decryption + switch decryptKey.Type { + case passvault.RSARecord: + // extract the aes key from the pubEncryptedKey + aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, pubEncryptedKey, nil) + if err != nil { + return + } + case passvault.ECCRecord: + // extract the aes key from the pubEncryptedKey + aesKey, err = ecdh.Decrypt(decryptKey.eccKey, pubEncryptedKey) + + if err != nil { + return + } + default: + return nil, errors.New("unknown type") + } + + // decrypt + aesSession, err := aes.NewCipher(aesKey) + if err != nil { + return + } + + for _, encShare := range in { + tmp := make([]byte, 16) + aesSession.Decrypt(tmp, encShare) + + out = append(out, tmp) + } + + cache.useKey(name, user, slot, labels) + + return +} From e652300f430f6802c77e519d26dc17749164d21f Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Wed, 27 May 2015 11:29:36 -0700 Subject: [PATCH 02/14] Vendored in Bren2010/MSP --- cryptor/cryptor.go | 18 +-- msp/README.md | 153 ++++++++++++++++++ msp/formatted.go | 189 ++++++++++++++++++++++ msp/formatted_test.go | 89 ++++++++++ msp/msp.go | 369 ++++++++++++++++++++++++++++++++++++++++++ msp/msp_test.go | 72 +++++++++ msp/raw.go | 218 +++++++++++++++++++++++++ msp/raw_test.go | 74 +++++++++ 8 files changed, 1173 insertions(+), 9 deletions(-) create mode 100644 msp/README.md create mode 100644 msp/formatted.go create mode 100644 msp/formatted_test.go create mode 100644 msp/msp.go create mode 100644 msp/msp_test.go create mode 100644 msp/raw.go create mode 100644 msp/raw_test.go diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 65aa035..5d0e701 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -15,8 +15,8 @@ import ( "sort" "strconv" - "github.com/Bren2010/msp" "github.com/cloudflare/redoctober/keycache" + "github.com/cloudflare/redoctober/msp" "github.com/cloudflare/redoctober/padding" "github.com/cloudflare/redoctober/passvault" "github.com/cloudflare/redoctober/symcrypt" @@ -52,11 +52,11 @@ type AccessStructure struct { // Implements msp.UserDatabase type UserDatabase struct { records *passvault.Records - cache *keycache.Cache + cache *keycache.Cache - user string - labels []string - keySet map[string]SingleWrappedKey + user string + labels []string + keySet map[string]SingleWrappedKey shareSet map[string][][]byte } @@ -433,10 +433,10 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u } db := msp.UserDatabase(UserDatabase{ - cache: cache, - user: user, - labels: encrypted.Labels, - keySet: encrypted.KeySetRSA, + cache: cache, + user: user, + labels: encrypted.Labels, + keySet: encrypted.KeySetRSA, shareSet: encrypted.ShareSet, }) unwrappedKey, err = sss.RecoverSecret(msp.Modulus(127), &db) diff --git a/msp/README.md b/msp/README.md new file mode 100644 index 0000000..051ec26 --- /dev/null +++ b/msp/README.md @@ -0,0 +1,153 @@ +Monotone Span Programs +====================== + +- [Introduction](#monotone-span-programs) + - [Types of Predicates](#types-of-predicates) +- [Documentation](#documentation) + - [User Databases](#user-databases) + - [Building Predicates](#building-predicates) + - [Splitting & Reconstructing Secrets](#splitting--reconstructing-secrets) + +A *Monotone Span Program* (or *MSP*) is a cryptographic technique for splitting +a secret into several *shares* that are then distributed to *parties* or +*users*. (Have you heard of [Shamir's Secret Sharing](http://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing)? It's like that.) + +Unlike Shamir's Secret Sharing, MSPs allow *arbitrary monotone access +structures*. An access structure is just a boolean predicate on a set of users +that tells us whether or not that set is allowed to recover the secret. A +monotone access structure is the same thing, but with the invariant that adding +a user to a set will never turn the predicate's output from `true` to +`false`--negations or boolean `nots` are disallowed. + +**Example:** `(Alice or Bob) and Carl` is good, but `(Alice or Bob) and !Carl` +is not because excluding people is rude. + +MSPs are fundamental and powerful primitives. They're well-suited for +distributed commitments (DC), verifiable secret sharing (VSS) and multi-party +computation (MPC). + + +#### Types of Predicates + +An MSP itself is a type of predicate and the reader is probably familiar with +raw boolean predicates like in the example above, but another important type is +a *formatted boolean predicate*. + +Formatted boolean predicates are isomorphic to all MSPs and therefore all +monotone raw boolean predicates. They're built by nesting threshold gates. + +**Example:** Let `(2, Alice, Bob, Carl)` denote that at least 2 members of the +set `{Alice, Bob, Carl}` must be present to recover the secret. Then, +`(2, (1, Alice, Bob), Carl)` is the formatted version of +`(Alice or Bob) and Carl`. + +It is possible to convert between different types of predicates (and its one of +the fundamental operations of splitting secrets with an MSP), but circuit +minimization is a non-trivial and computationally complex problem. The code can +do a small amount of compression, but the onus is on the user to design +efficiently computable predicates. + + +#### To Do + +1. Anonymous secret generation / secret homomorphisms +2. Non-interactive verifiable secret sharing / distributed commitments + + +Documentation +------------- + +### User Databases + +```go +type UserDatabase interface { + ValidUser(string) bool // Is this the name of an existing user? + CanGetShare(string) bool // Can I get this user's share? + GetShare(string) ([][]byte, error) // Retrieves a user's shares. +} +``` + +User databases are an abstraction over the primitive name -> share map that +hopefully offer a bit more flexibility in implementing secret sharing schemes. +`CanGetShare(name)` should be faster and less binding than `GetShare(name)`. +`CanGetShare(name)` may be called a large number of times, but `GetShare(name)` +will be called the absolute minimum number of times possible. + +Depending on the predicate used, a name may be associated to multiple shares of +a secret, hence `[][]byte` as opposed to one share (`[]byte`). + +For test/play purposes there's a cheaty implementation of `UserDatabase` in +`msp_test.go` that just wraps the `map[string][][]byte` returned by +`DistributeShares(...)` + +### Building Predicates + +```go +type Raw struct { ... } + +func StringToRaw(string) (Raw, error) { ... } +func (r Raw) String() string { .. .} +func (r Raw) Formatted() Formatted { ... } + + +type Formatted struct { ... } + +func StringToFormatted(string) (Formatted, error) +func (f Formatted) String() string +``` + +Building predicates is extremely easy--just write it out in a string and have +one of the package methods parse it. + +Raw predicates take the `&` (logical AND) and `|` (logical OR) operators, but +are otherwise the same as discussed above. Formatted predicates are exactly the +same as above--just nested threshold gates. + +```go +r1, _ := msp.StringToRaw("(Alice | Bob) & Carl") +r2, _ := msp.StringToRaw("Alice & Bob & Carl") + +fmt.Printf("%v\n", r1.Formatted()) // (2, (1, Alice, Bob), Carl) +fmt.Printf("%v\n", r2.Formatted()) // (3, Alice, Bob, Carl) +``` + +### Splitting & Reconstructing Secrets + +```go +type MSP Formatted + +func Modulus(n int) *big.Int {} +func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (map[string][][]byte, error) {} +func (m MSP) RecoverSecret(modulus *big.Int, db *UserDatabase) ([]byte, error) {} +``` + +To switch from predicate-mode to secret-sharing-mode, just cast your formatted +predicate into an MSP something like this: +```go +predicate := msp.StringToFormatted("(3, Alice, Bob, Carl)") +sss := msp.MSP(predicate) +``` + +Calling `DistributeShares` on it returns a map from a party's name to their set +of shares which should be given to that party and integrated into the +`UserDatabase` somehow. When you're ready to reconstruct the secret, you call +`RecoverSecret`, which does some prodding about and hopefully gives you back +what you put in. + +The modulus determines the size of the secret shares. It must be prime, larger +than the secret, and larger than nk where `n` is the number of +parties and `k` is the depth of the circuit. (For each path from the root to a +gate with only leaf nodes, sum the thresholds of all the gates along that path. +`k` is the maximum of these values--a safe overestimation is to sum the +thresholds of all gates in the circuit) Because this form of secret sharing +is *information-theoretically secure*, as long as the modulus satisfies those +requirements, it can be as small or as large as you'd like (there's no +bonus/reduction in security). An excessively small modulus is restrictive, but +saves a *lot* of bandwidth. However, there's no sensible reason for a modulus +over about 256 bits--by that point, it's easier to generate a random AES key, +encrypt your secret with *that*, and distribute shares of the decryption key +instead. + +For convenience, the `Modulus` function has some hard coded moduli that are +useful. The choices are currently: `127`, `224`, and `256` for a 127-bit, +224-bit, or 256-bit modulus, respectively. diff --git a/msp/formatted.go b/msp/formatted.go new file mode 100644 index 0000000..c4d16c2 --- /dev/null +++ b/msp/formatted.go @@ -0,0 +1,189 @@ +package msp + +import ( + "container/list" + "errors" + "fmt" + "strconv" + "strings" +) + +type Formatted struct { // Represents threshold gate (also type of condition) + Min int + Conds []Condition +} + +func StringToFormatted(f string) (out Formatted, err error) { + // Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing + // infix notation. Running time linear in the size of the predicate? + // + // Steps either to the next comma or the next unparenthesis. + // ( -> Push new queue onto staging stack + // value -> Push onto back of queue at top of staging stack. + // ) -> Pop queue off top of staging stack, build threshold gate, + // and push gate onto the back of the top queue. + // + // Staging stack is empty on initialization and should have exactly 1 built + // threshold gate at the end of the string. + if f[0] != '(' || f[len(f)-1] != ')' { + return out, errors.New("Invalid string--wrong format.") + } + + getNext := func(f string) (string, string) { // f -> (next, rest) + f = strings.TrimSpace(f) + + if f[0] == '(' { + return f[0:1], f[1:] + } + + nextComma := strings.Index(f, ",") + if f[0] == ')' { + if nextComma == -1 { + return f[0:1], "" + } + return f[0:1], f[nextComma+1:] + } else if nextComma == -1 { + return f[0 : len(f)-1], f[len(f)-1:] + } + + nextUnParen := strings.Index(f, ")") + if nextComma < nextUnParen { + return strings.TrimSpace(f[0:nextComma]), f[nextComma+1:] + } + + return strings.TrimSpace(f[0:nextUnParen]), f[nextUnParen:] + } + + staging := list.New() + indices := make(map[string]int, 0) + + var nxt string + for len(f) > 0 { + nxt, f = getNext(f) + + switch nxt { + case "(": + staging.PushFront(list.New()) + case ")": + top := staging.Remove(staging.Front()).(*list.List) + + var min int + minStr := top.Front() + min, err = strconv.Atoi(minStr.Value.(String).string) + if err != nil { + return + } + + built := Formatted{ + Min: min, + Conds: []Condition{}, + } + + for cond := minStr.Next(); cond != nil; cond = cond.Next() { + built.Conds = append(built.Conds, cond.Value.(Condition)) + } + + if staging.Len() == 0 { + if len(f) == 0 { + return built, nil + } + return built, errors.New("Invalid string--terminated early.") + } + + staging.Front().Value.(*list.List).PushBack(built) + + default: + if _, there := indices[nxt]; !there { + indices[nxt] = 0 + } + + staging.Front().Value.(*list.List).PushBack(String{nxt, indices[nxt]}) + indices[nxt]++ + } + } + + return out, errors.New("Invalid string--never terminated.") +} + +func (f Formatted) String() string { + out := fmt.Sprintf("(%v", f.Min) + + for _, cond := range f.Conds { + switch cond.(type) { + case String: + out += fmt.Sprintf(", %v", cond.(String).string) + case Formatted: + out += fmt.Sprintf(", %v", (cond.(Formatted)).String()) + } + } + + return out + ")" +} + +func (f Formatted) Ok(db *UserDatabase) bool { + // Goes through the smallest number of conditions possible to check if the + // threshold gate returns true. Sometimes requires recursing down to check + // nested threshold gates. + rest := f.Min + + for _, cond := range f.Conds { + if cond.Ok(db) { + rest-- + } + + if rest == 0 { + return true + } + } + + return false +} + +func (f *Formatted) Compress() { + if f.Min == len(f.Conds) { + // AND Compression: (n, ..., (m, ...), ...) = (n + m, ...) + skip := 0 + for i, cond := range f.Conds { + if skip > 0 { + skip-- + continue + } + + switch cond.(type) { + case Formatted: + cond := cond.(Formatted) + cond.Compress() + f.Conds[i] = cond + + if cond.Min == len(cond.Conds) { + f.Min += cond.Min - 1 + f.Conds = append(f.Conds[0:i], + append(cond.Conds, f.Conds[i+1:]...)...) + skip = len(cond.Conds) - 1 + } + } + } + } else if f.Min == 1 { + // OR Compression: (1, ..., (1, ...), ...) = (1, ...) + skip := 0 + for i, cond := range f.Conds { + if skip > 0 { + skip-- + continue + } + + switch cond.(type) { + case Formatted: + cond := cond.(Formatted) + cond.Compress() + f.Conds[i] = cond + + if cond.Min == 1 { + f.Conds = append(f.Conds[0:i], + append(cond.Conds, f.Conds[i+1:]...)...) + skip = len(cond.Conds) - 1 + } + } + } + } +} diff --git a/msp/formatted_test.go b/msp/formatted_test.go new file mode 100644 index 0000000..a693cee --- /dev/null +++ b/msp/formatted_test.go @@ -0,0 +1,89 @@ +package msp + +import ( + "testing" +) + +func TestFormatted(t *testing.T) { + query1 := Formatted{ + Min: 2, + Conds: []Condition{ + String{"Alice", 0}, String{"Bob", 0}, String{"Carl", 0}, + }, + } + + query2 := Formatted{ + Min: 3, + Conds: []Condition{ + String{"Alice", 0}, String{"Bob", 0}, String{"Carl", 0}, + }, + } + + query3 := Formatted{ + Min: 2, + Conds: []Condition{ + Formatted{ + Min: 1, + Conds: []Condition{ + String{"Alice", 0}, String{"Bob", 0}, + }, + }, + String{"Carl", 0}, + }, + } + + query4 := Formatted{ + Min: 2, + Conds: []Condition{ + Formatted{ + Min: 1, + Conds: []Condition{ + String{"Alice", 0}, String{"Carl", 0}, + }, + }, + String{"Bob", 0}, + }, + } + + db := UserDatabase(Database(map[string][][]byte{ + "Alice": [][]byte{[]byte("blah")}, + "Carl": [][]byte{[]byte("herp")}, + })) + + if query1.Ok(&db) != true { + t.Fatalf("Query #1 was wrong.") + } + + if query2.Ok(&db) != false { + t.Fatalf("Query #2 was wrong.") + } + + if query3.Ok(&db) != true { + t.Fatalf("Query #3 was wrong.") + } + + if query4.Ok(&db) != false { + t.Fatalf("Query #4 was wrong.") + } + + query1String := "(2, Alice, Bob, Carl)" + query3String := "(2, (1, Alice, Bob), Carl)" + + if query1.String() != query1String { + t.Fatalf("Query #1 String was wrong; %v", query1.String()) + } + + if query3.String() != query3String { + t.Fatalf("Query #3 String was wrong; %v", query3.String()) + } + + decQuery1, err := StringToFormatted(query1String) + if err != nil || decQuery1.String() != query1String { + t.Fatalf("Query #1 decoded wrong: %v %v", decQuery1.String(), err) + } + + decQuery3, err := StringToFormatted(query3String) + if err != nil || decQuery3.String() != query3String { + t.Fatalf("Query #3 decoded wrong: %v %v", decQuery3.String(), err) + } +} diff --git a/msp/msp.go b/msp/msp.go new file mode 100644 index 0000000..3642593 --- /dev/null +++ b/msp/msp.go @@ -0,0 +1,369 @@ +package msp + +import ( + "container/heap" + "crypto/rand" + "errors" + "math/big" + "strings" +) + +// A UserDatabase is an abstraction over the name -> share map returned by the +// secret splitter that allows an application to only decrypt or request shares +// when needed, rather than re-build a partial map of known data. +type UserDatabase interface { + ValidUser(name string) bool + CanGetShare(string) bool + GetShare(string) ([][]byte, error) +} + +type Condition interface { // Represents one condition in a predicate + Ok(*UserDatabase) bool +} + +type String struct { // Type of condition + string + index int +} + +func (s String) Ok(db *UserDatabase) bool { + return (*db).CanGetShare(s.string) +} + +type TraceElem struct { + loc int + names []string + trace []string +} + +type TraceSlice []TraceElem + +func (ts TraceSlice) Len() int { return len(ts) } +func (ts TraceSlice) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } + +func (ts TraceSlice) Less(i, j int) bool { + return len(ts[i].trace) < len(ts[j].trace) +} + +func (ts *TraceSlice) Push(te interface{}) { *ts = append(*ts, te.(TraceElem)) } +func (ts *TraceSlice) Pop() interface{} { + out := (*ts)[0] + *ts = (*ts)[1 : len(*ts)-1] + + return out +} + +func (ts TraceSlice) Compact() (index []int, names []string, trace []string) { + for _, te := range ts { + index = append(index, te.loc) + names = append(names, te.names...) + trace = append(trace, te.trace...) + } + + ptr, cutoff := 0, len(trace) + +TopLoop: + for ptr < cutoff { + for i := 0; i < ptr; i++ { + if trace[i] == trace[ptr] { + trace[ptr], trace[cutoff-1] = trace[cutoff-1], trace[ptr] + cutoff-- + + continue TopLoop + } + } + + ptr++ + } + trace = trace[0:cutoff] + + return +} + +type MSP Formatted + +func Modulus(n int) (modulus *big.Int) { + switch n { + case 256: + modulus = big.NewInt(1) // 2^256 - 2^224 + 2^192 + 2^96 - 1 + modulus.Lsh(modulus, 256) + modulus.Sub(modulus, big.NewInt(0).Lsh(big.NewInt(1), 224)) + modulus.Add(modulus, big.NewInt(0).Lsh(big.NewInt(1), 192)) + modulus.Add(modulus, big.NewInt(0).Lsh(big.NewInt(1), 96)) + modulus.Sub(modulus, big.NewInt(1)) + + case 224: + modulus = big.NewInt(1) // 2^224 - 2^96 + 1 + modulus.Lsh(modulus, 224) + modulus.Sub(modulus, big.NewInt(0).Lsh(big.NewInt(1), 96)) + modulus.Add(modulus, big.NewInt(1)) + + default: // Silent fail. + modulus = big.NewInt(1) // 2^127 - 1 + modulus.Lsh(modulus, 127) + modulus.Sub(modulus, big.NewInt(1)) + } + + return +} + +func StringToMSP(pred string) (m MSP, err error) { + var f Formatted + + if -1 == strings.Index(pred, ",") { + var r Raw + r, err = StringToRaw(pred) + if err != nil { + return + } + + f = r.Formatted() + } else { + f, err = StringToFormatted(pred) + if err != nil { + return + } + } + + return MSP(f), nil +} + +func (m MSP) DerivePath(db *UserDatabase) (ok bool, names []string, locs []int, trace []string) { + ts := &TraceSlice{} + + for i, cond := range m.Conds { + switch cond.(type) { + case String: + if (*db).CanGetShare(cond.(String).string) { + heap.Push(ts, TraceElem{ + i, + []string{cond.(String).string}, + []string{cond.(String).string}, + }) + } + + case Formatted: + sok, _, _, strace := MSP(cond.(Formatted)).DerivePath(db) + if !sok { + continue + } + + heap.Push(ts, TraceElem{i, []string{}, strace}) + } + + if (*ts).Len() > m.Min { + *ts = (*ts)[0:m.Min] + } + } + + ok = (*ts).Len() >= m.Min + locs, names, trace = ts.Compact() + return +} + +func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (map[string][][]byte, error) { + out := make(map[string][][]byte) + + // Math to distribute shares. + secInt := big.NewInt(0).SetBytes(sec) // Convert secret to number. + secInt.Mod(secInt, modulus) + + var junk []*big.Int // Generate junk numbers. + for i := 1; i < m.Min; i++ { + r := make([]byte, (modulus.BitLen()/8)+1) + _, err := rand.Read(r) + if err != nil { + return out, err + } + + s := big.NewInt(0).SetBytes(r) + s.Mod(s, modulus) + + junk = append(junk, s) + } + + for i, cond := range m.Conds { // Calculate shares. + share := big.NewInt(1) + share.Mul(share, secInt) + + for j := 2; j <= m.Min; j++ { + cell := big.NewInt(int64(i + 1)) + cell.Exp(cell, big.NewInt(int64(j-1)), modulus) + // CELL SHOULD ALWAYS BE LESS THAN MODULUS + + share.Add(share, cell.Mul(cell, junk[j-2])).Mod(share, modulus) + } + + switch cond.(type) { + case String: + name := cond.(String).string + if _, ok := out[name]; ok { + out[name] = append(out[name], share.Bytes()) + } else if (*db).ValidUser(name) { + out[name] = [][]byte{share.Bytes()} + } else { + return out, errors.New("Unknown user in predicate.") + } + + default: + below := MSP(cond.(Formatted)) + subOut, err := below.DistributeShares(share.Bytes(), modulus, db) + if err != nil { + return out, err + } + + for name, shares := range subOut { + if _, ok := out[name]; ok { + out[name] = append(out[name], shares...) + } else { + out[name] = shares + } + + } + } + } + + return out, nil +} + +func (m MSP) RecoverSecret(modulus *big.Int, db *UserDatabase) ([]byte, error) { + cache := make(map[string][][]byte, 0) // Caches un-used shares for a user. + return m.recoverSecret(modulus, db, cache) +} + +func (m MSP) recoverSecret(modulus *big.Int, db *UserDatabase, cache map[string][][]byte) ([]byte, error) { + var ( + index = []int{} // Indexes where given shares were in the matrix. + shares = [][]byte{} // Contains shares that will be used in reconstruction. + ) + + ok, names, locs, _ := m.DerivePath(db) + if !ok { + return nil, errors.New("Not enough shares to recover.") + } + + for _, name := range names { + if _, cached := cache[name]; !cached { + out, err := (*db).GetShare(name) + if err != nil { + return nil, err + } + + cache[name] = out + } + } + + for _, loc := range locs { + gate := m.Conds[loc] + index = append(index, loc+1) + + switch gate.(type) { + case String: + if len(cache[gate.(String).string]) <= gate.(String).index { + return nil, errors.New("Predicate / database mismatch!") + } + + shares = append(shares, cache[gate.(String).string][gate.(String).index]) + + case Formatted: + share, err := MSP(gate.(Formatted)).recoverSecret(modulus, db, cache) + if err != nil { + return nil, err + } + + shares = append(shares, share) + } + } + + // Calculate the reconstruction vector. We only need the top row of the + // matrix's inverse, so we augment M transposed with u1 transposed and + // eliminate Gauss-Jordan style. + matrix := [][][2]int{} // 2d grid of (numerator, denominator) + matrix = append(matrix, [][2]int{}) // Create first row of all 1s + + for j := 0; j < m.Min; j++ { + matrix[0] = append(matrix[0], [2]int{1, 1}) + } + + for j := 1; j < m.Min; j++ { // Fill in rest of matrix. + row := [][2]int{} + + for k, idx := range index { + row = append(row, [2]int{int(idx) * matrix[j-1][k][0], matrix[j-1][k][1]}) + } + + matrix = append(matrix, row) + } + + matrix[0] = append(matrix[0], [2]int{1, 1}) // Stick on last column. + for j := 1; j < m.Min; j++ { + matrix[j] = append(matrix[j], [2]int{0, 1}) + } + + // Reduce matrix. + for i := 0; i < len(matrix); i++ { + for j := 0; j < len(matrix[i]); j++ { // Make row unary. + if i == j { + continue + } + + matrix[i][j][0] *= matrix[i][i][1] + matrix[i][j][1] *= matrix[i][i][0] + } + matrix[i][i] = [2]int{1, 1} + + for j := 0; j < len(matrix); j++ { // Remove this row from the others. + if i == j { + continue + } + + top := matrix[j][i][0] + bot := matrix[j][i][1] + + for k := 0; k < len(matrix[j]); k++ { + // matrix[j][k] = matrix[j][k] - matrix[j][i] * matrix[i][k] + temp := [2]int{0, 0} + temp[0] = top * matrix[i][k][0] + temp[1] = bot * matrix[i][k][1] + + if matrix[j][k][0] == 0 { + matrix[j][k][0] = -temp[0] + matrix[j][k][1] = temp[1] + } else { + matrix[j][k][0] = (matrix[j][k][0] * temp[1]) - (temp[0] * matrix[j][k][1]) + matrix[j][k][1] *= temp[1] + } + + if matrix[j][k][0] == 0 { + matrix[j][k][1] = 1 + } + } + } + } + + // Compute dot product of the shares vector and the reconstruction vector to + // reconstruct the secret. + size := len(modulus.Bytes()) + out := make([]byte, size) + secInt := big.NewInt(0) + + for i, share := range shares { + lst := len(matrix[i]) - 1 + + num := big.NewInt(int64(matrix[i][lst][0])) + den := big.NewInt(int64(matrix[i][lst][1])) + num.Mod(num, modulus) + den.Mod(den, modulus) + + coeff := big.NewInt(0).ModInverse(den, modulus) + coeff.Mul(coeff, num).Mod(coeff, modulus) + + shareInt := big.NewInt(0).SetBytes(share) + shareInt.Mul(shareInt, coeff).Mod(shareInt, modulus) + + secInt.Add(secInt, shareInt).Mod(secInt, modulus) + } + + out = append(out, secInt.Bytes()...) + return out[len(out)-size:], nil +} diff --git a/msp/msp_test.go b/msp/msp_test.go new file mode 100644 index 0000000..bf3e460 --- /dev/null +++ b/msp/msp_test.go @@ -0,0 +1,72 @@ +package msp + +import ( + "bytes" + "crypto/rand" + "errors" + "testing" +) + +type Database map[string][][]byte + +func (d Database) ValidUser(name string) bool { + _, ok := d[name] + return ok +} + +func (d Database) CanGetShare(name string) bool { + _, ok := d[name] + return ok +} + +func (d Database) GetShare(name string) ([][]byte, error) { + out, ok := d[name] + + if ok { + return out, nil + } else { + return nil, errors.New("Not found!") + } +} + +func TestMSP(t *testing.T) { + db := UserDatabase(Database(map[string][][]byte{ + "Alice": [][]byte{}, + "Bob": [][]byte{}, + "Carl": [][]byte{}, + })) + + sec := make([]byte, 16) + rand.Read(sec) + sec[0] &= 63 // Removes first 2 bits of key. + + predicate, _ := StringToMSP("(2, (1, Alice, Bob), Carl)") + + shares1, _ := predicate.DistributeShares(sec, Modulus(127), &db) + shares2, _ := predicate.DistributeShares(sec, Modulus(127), &db) + + alice := bytes.Compare(shares1["Alice"][0], shares2["Alice"][0]) + bob := bytes.Compare(shares1["Bob"][0], shares2["Bob"][0]) + carl := bytes.Compare(shares1["Carl"][0], shares2["Carl"][0]) + + if alice == 0 && bob == 0 && carl == 0 { + t.Fatalf("Key splitting isn't random! %v %v", shares1, shares2) + } + + db1 := UserDatabase(Database(shares1)) + db2 := UserDatabase(Database(shares2)) + + sec1, err := predicate.RecoverSecret(Modulus(127), &db1) + if err != nil { + t.Fatalf("#1: %v", err) + } + + sec2, err := predicate.RecoverSecret(Modulus(127), &db2) + if err != nil { + t.Fatalf("#2: %v", err) + } + + if !(bytes.Compare(sec, sec1) == 0 && bytes.Compare(sec, sec2) == 0) { + t.Fatalf("Secrets derived differed: %v %v %v", sec, sec1, sec2) + } +} diff --git a/msp/raw.go b/msp/raw.go new file mode 100644 index 0000000..cd2506b --- /dev/null +++ b/msp/raw.go @@ -0,0 +1,218 @@ +package msp + +import ( + "container/list" + "errors" + "strings" +) + +type NodeType int // Types of node in the binary expression tree. + +const ( + NodeAnd NodeType = iota + NodeOr +) + +func (t NodeType) Type() NodeType { + return t +} + +type Raw struct { // Represents one node in the tree. + NodeType + + Left *Condition + Right *Condition +} + +func StringToRaw(r string) (out Raw, err error) { + // Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing + // infix notation. Reads one long unbroken expression (several operators and + // operands with no parentheses) at a time and parses it into a binary + // expression tree (giving AND operators precedence). Running time linear in + // the size of the predicate? + // + // Steps to the next (un)parenthesis. + // ( -> Push new queue onto staging stack + // value -> Push onto back of queue at top of staging stack. + // ) -> Pop queue off top of staging stack, build BET, and push tree + // onto the back of the top queue. + // + // To build the binary expression tree, for each type of operation we iterate + // through the (Condition, operator) lists compacting where that operation + // occurs into tree nodes. + // + // Staging stack is empty on initialization and should have exactly 1 node + // (the root node) at the end of the string. + r = "(" + r + ")" + + min := func(a, b, c int) int { // Return smallest non-negative argument. + if a > b { // Sort {a, b, c} + a, b = b, a + } + if b > c { + b, c = c, b + } + if a > b { + a, b = b, a + } + + if a != -1 { + return a + } else if b != -1 { + return b + } else { + return c + } + } + + getNext := func(r string) (string, string) { // r -> (next, rest) + r = strings.TrimSpace(r) + + if r[0] == '(' || r[0] == ')' || r[0] == '&' || r[0] == '|' { + return r[0:1], r[1:] + } + + nextOper := min( + strings.Index(r, "&"), + strings.Index(r, "|"), + strings.Index(r, ")"), + ) + + if nextOper == -1 { + return r, "" + } + return strings.TrimSpace(r[0:nextOper]), r[nextOper:] + } + + staging := list.New() // Stack of (Condition list, operator list) + indices := make(map[string]int, 0) + + var nxt string + for len(r) > 0 { + nxt, r = getNext(r) + + switch nxt { + case "(": + staging.PushFront([2]*list.List{list.New(), list.New()}) + case ")": + top := staging.Remove(staging.Front()).([2]*list.List) + if top[0].Len() != (top[1].Len() + 1) { + return out, errors.New("Stacks are invalid size.") + } + + for typ := NodeAnd; typ <= NodeOr; typ++ { + var step *list.Element + leftOperand := top[0].Front() + + for oper := top[1].Front(); oper != nil; oper = step { + step = oper.Next() + + if oper.Value.(NodeType) == typ { + left := leftOperand.Value.(Condition) + right := leftOperand.Next().Value.(Condition) + + leftOperand.Next().Value = Raw{ + NodeType: typ, + Left: &left, + Right: &right, + } + + leftOperand = leftOperand.Next() + + top[0].Remove(leftOperand.Prev()) + top[1].Remove(oper) + } else { + leftOperand = leftOperand.Next() + } + } + } + + if top[0].Len() != 1 || top[1].Len() != 0 { + return out, errors.New("Invalid expression--couldn't evaluate.") + } + + if staging.Len() == 0 { + if len(r) == 0 { + return top[0].Front().Value.(Raw), nil + } + return out, errors.New("Invalid string--terminated early.") + } + staging.Front().Value.([2]*list.List)[0].PushBack(top[0].Front().Value) + + case "&": + staging.Front().Value.([2]*list.List)[1].PushBack(NodeAnd) + case "|": + staging.Front().Value.([2]*list.List)[1].PushBack(NodeOr) + default: + if _, there := indices[nxt]; !there { + indices[nxt] = 0 + } + + staging.Front().Value.([2]*list.List)[0].PushBack(String{nxt, indices[nxt]}) + indices[nxt]++ + } + } + + return out, errors.New("Invalid string--never terminated.") +} + +func (r Raw) String() string { + out := "" + + switch (*r.Left).(type) { + case String: + out += (*r.Left).(String).string + default: + out += "(" + (*r.Left).(Raw).String() + ")" + } + + if r.Type() == NodeAnd { + out += " & " + } else { + out += " | " + } + + switch (*r.Right).(type) { + case String: + out += (*r.Right).(String).string + default: + out += "(" + (*r.Right).(Raw).String() + ")" + } + + return out +} + +func (r Raw) Formatted() (out Formatted) { + // Recursively maps a raw predicate to a formatted predicate by mapping AND + // gates to (2, A, B) treshold gates and OR gates to (1, A, B) gates. + if r.Type() == NodeAnd { + out.Min = 2 + } else { + out.Min = 1 + } + + switch (*r.Left).(type) { + case String: + out.Conds = []Condition{(*r.Left).(String)} + default: + out.Conds = []Condition{(*r.Left).(Raw).Formatted()} + } + + switch (*r.Right).(type) { + case String: + out.Conds = append(out.Conds, (*r.Right).(String)) + default: + out.Conds = append(out.Conds, (*r.Right).(Raw).Formatted()) + } + + out.Compress() // Small amount of predicate compression. + return +} + +func (r Raw) Ok(db *UserDatabase) bool { + if r.Type() == NodeAnd { + return (*r.Left).Ok(db) && (*r.Right).Ok(db) + } else { + return (*r.Left).Ok(db) || (*r.Right).Ok(db) + } +} diff --git a/msp/raw_test.go b/msp/raw_test.go new file mode 100644 index 0000000..c9e69ab --- /dev/null +++ b/msp/raw_test.go @@ -0,0 +1,74 @@ +package msp + +import ( + "testing" +) + +func TestRaw(t *testing.T) { + alice := Condition(String{"Alice", 0}) + bob := Condition(String{"Bob", 0}) + carl := Condition(String{"Carl", 0}) + + query1 := Raw{ + NodeType: NodeAnd, + Left: &alice, + Right: &bob, + } + + aliceOrBob := Condition(Raw{ + NodeType: NodeOr, + Left: &alice, + Right: &bob, + }) + + query2 := Raw{ + NodeType: NodeAnd, + Left: &aliceOrBob, + Right: &carl, + } + + db := UserDatabase(Database(map[string][][]byte{ + "Alice": [][]byte{[]byte("blah")}, + "Carl": [][]byte{[]byte("herp")}, + })) + + if query1.Ok(&db) != false { + t.Fatalf("Query #1 was wrong.") + } + + if query2.Ok(&db) != true { + t.Fatalf("Query #2 was wrong.") + } + + query1String := "Alice & Bob" + query2String := "(Alice | Bob) & Carl" + + if query1.String() != query1String { + t.Fatalf("Query #1 String was wrong; %v", query1.String()) + } + + if query2.String() != query2String { + t.Fatalf("Query #2 String was wrong; %v", query2.String()) + } + + decQuery1, err := StringToRaw(query1String) + if err != nil || decQuery1.String() != query1String { + t.Fatalf("Query #1 decoded wrong: %v %v", decQuery1.String(), err) + } + + decQuery2, err := StringToRaw(query2String) + if err != nil || decQuery2.String() != query2String { + t.Fatalf("Query #2 decoded wrong: %v %v", decQuery2.String(), err) + } + + formattedQuery1String := "(2, Alice, Bob)" + formattedQuery2String := "(2, (1, Alice, Bob), Carl)" + + if query1.Formatted().String() != formattedQuery1String { + t.Fatalf("Query #1 formatted wrong: %v", query1.Formatted().String()) + } + + if query2.Formatted().String() != formattedQuery2String { + t.Fatalf("Query #2 formatted wrong: %v", query2.Formatted().String()) + } +} From 9e514e902abc6f1631d8f7f29f9570fb76194a29 Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Wed, 27 May 2015 12:05:39 -0700 Subject: [PATCH 03/14] Shorten key by 2 bits. --- cryptor/cryptor.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 5d0e701..400c371 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -466,6 +466,11 @@ func (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (r if err != nil { return } + if len(access.Predicate) > 0 { + // The first two bits of the key need to be removed to prevent wrapping + // because the modulus is slightly too small. + clearKey[0] &= 63 + } err = encrypted.wrapKey(c.records, clearKey, access) if err != nil { From 4c161e343cd6ca64206fc59bcac338b75691ef1d Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Mon, 10 Aug 2015 14:58:57 -0700 Subject: [PATCH 04/14] Write better error messages. --- msp/formatted.go | 6 +++--- msp/raw.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/msp/formatted.go b/msp/formatted.go index c4d16c2..2414bf7 100644 --- a/msp/formatted.go +++ b/msp/formatted.go @@ -26,7 +26,7 @@ func StringToFormatted(f string) (out Formatted, err error) { // Staging stack is empty on initialization and should have exactly 1 built // threshold gate at the end of the string. if f[0] != '(' || f[len(f)-1] != ')' { - return out, errors.New("Invalid string--wrong format.") + return out, errors.New("Invalid string: Needs to begin and end with parentheses.") } getNext := func(f string) (string, string) { // f -> (next, rest) @@ -87,7 +87,7 @@ func StringToFormatted(f string) (out Formatted, err error) { if len(f) == 0 { return built, nil } - return built, errors.New("Invalid string--terminated early.") + return built, errors.New("Invalid string: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?") } staging.Front().Value.(*list.List).PushBack(built) @@ -102,7 +102,7 @@ func StringToFormatted(f string) (out Formatted, err error) { } } - return out, errors.New("Invalid string--never terminated.") + return out, errors.New("Invalid string: Not finished parsing, but out of data. Too many opening parentheses or too few closing parentheses?") } func (f Formatted) String() string { diff --git a/msp/raw.go b/msp/raw.go index cd2506b..baae187 100644 --- a/msp/raw.go +++ b/msp/raw.go @@ -97,7 +97,7 @@ func StringToRaw(r string) (out Raw, err error) { case ")": top := staging.Remove(staging.Front()).([2]*list.List) if top[0].Len() != (top[1].Len() + 1) { - return out, errors.New("Stacks are invalid size.") + return out, errors.New("Invalid string: There needs to be an operator (& or |) for every pair of operands.") } for typ := NodeAnd; typ <= NodeOr; typ++ { @@ -128,14 +128,14 @@ func StringToRaw(r string) (out Raw, err error) { } if top[0].Len() != 1 || top[1].Len() != 0 { - return out, errors.New("Invalid expression--couldn't evaluate.") + return out, errors.New("Invalid string: Couldn't evaluate all of the operators.") } if staging.Len() == 0 { if len(r) == 0 { return top[0].Front().Value.(Raw), nil } - return out, errors.New("Invalid string--terminated early.") + return out, errors.New("Invalid string: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?") } staging.Front().Value.([2]*list.List)[0].PushBack(top[0].Front().Value) @@ -153,7 +153,7 @@ func StringToRaw(r string) (out Raw, err error) { } } - return out, errors.New("Invalid string--never terminated.") + return out, errors.New("Invalid string: Not finished parsing, but out of data. Too many opening parentheses or too few closing parentheses?") } func (r Raw) String() string { From 701b938562e61e337637be60d3fc9681f117eaa3 Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Wed, 4 Nov 2015 11:45:32 -0800 Subject: [PATCH 05/14] Import bug fixes from MSP. --- msp/msp.go | 25 ++++++++++++++++++------- msp/raw.go | 9 ++++++++- msp/raw_test.go | 8 ++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/msp/msp.go b/msp/msp.go index 3642593..c7e3db3 100644 --- a/msp/msp.go +++ b/msp/msp.go @@ -198,16 +198,16 @@ func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (m case String: name := cond.(String).string if _, ok := out[name]; ok { - out[name] = append(out[name], share.Bytes()) + out[name] = append(out[name], m.encode(share, modulus)) } else if (*db).ValidUser(name) { - out[name] = [][]byte{share.Bytes()} + out[name] = [][]byte{m.encode(share, modulus)} } else { return out, errors.New("Unknown user in predicate.") } default: below := MSP(cond.(Formatted)) - subOut, err := below.DistributeShares(share.Bytes(), modulus, db) + subOut, err := below.DistributeShares(m.encode(share, modulus), modulus, db) if err != nil { return out, err } @@ -343,8 +343,6 @@ func (m MSP) recoverSecret(modulus *big.Int, db *UserDatabase, cache map[string] // Compute dot product of the shares vector and the reconstruction vector to // reconstruct the secret. - size := len(modulus.Bytes()) - out := make([]byte, size) secInt := big.NewInt(0) for i, share := range shares { @@ -364,6 +362,19 @@ func (m MSP) recoverSecret(modulus *big.Int, db *UserDatabase, cache map[string] secInt.Add(secInt, shareInt).Mod(secInt, modulus) } - out = append(out, secInt.Bytes()...) - return out[len(out)-size:], nil + return m.encode(secInt, modulus), nil +} + +func (m MSP) encode(x *big.Int, modulus *big.Int) (out []byte) { + tmp := x.Bytes() + tmpSize := len(tmp) + + modSize := len(modulus.Bytes()) + out = make([]byte, modSize) + + for pos := 0; pos < tmpSize; pos++ { + out[modSize-pos-1] = tmp[tmpSize-pos-1] + } + + return out } diff --git a/msp/raw.go b/msp/raw.go index baae187..cc939b4 100644 --- a/msp/raw.go +++ b/msp/raw.go @@ -133,7 +133,14 @@ func StringToRaw(r string) (out Raw, err error) { if staging.Len() == 0 { if len(r) == 0 { - return top[0].Front().Value.(Raw), nil + res := top[0].Front().Value + + switch res.(type) { + case Raw: + return res.(Raw), nil + default: + return out, errors.New("Invalid string: Only one condition was found.") + } } return out, errors.New("Invalid string: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?") } diff --git a/msp/raw_test.go b/msp/raw_test.go index c9e69ab..1343c10 100644 --- a/msp/raw_test.go +++ b/msp/raw_test.go @@ -72,3 +72,11 @@ func TestRaw(t *testing.T) { t.Fatalf("Query #2 formatted wrong: %v", query2.Formatted().String()) } } + +func TestOneCondition(t *testing.T) { + _, err := StringToRaw("(Alice or Bob)") + + if err == nil { + t.Fatalf("A predicate with only one condition should fail to parse!") + } +} From d93709973e3365c7f89365e125b273c9a971e908 Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Wed, 4 Nov 2015 14:31:04 -0800 Subject: [PATCH 06/14] Track delegates when decrypting with shares. --- cryptor/cryptor.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 400c371..3289eb6 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -51,6 +51,8 @@ type AccessStructure struct { // Implements msp.UserDatabase type UserDatabase struct { + names *[]string + records *passvault.Records cache *keycache.Cache @@ -71,6 +73,8 @@ func (u UserDatabase) CanGetShare(name string) bool { } func (u UserDatabase) GetShare(name string) ([][]byte, error) { + *u.names = append(*u.names, name) + return u.cache.DecryptShares( u.shareSet[name], name, @@ -433,6 +437,7 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u } db := msp.UserDatabase(UserDatabase{ + names: &names, cache: cache, user: user, labels: encrypted.Labels, @@ -440,7 +445,6 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u shareSet: encrypted.ShareSet, }) unwrappedKey, err = sss.RecoverSecret(msp.Modulus(127), &db) - names = []string{"Shares"} return } From 68e5403a7b83331629c8a4f018fd4637a427bb98 Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Wed, 4 Nov 2015 15:01:19 -0800 Subject: [PATCH 07/14] Add predicate section to UI. --- index.html | 117 +++++++++++++++++++++++++++++++++----------------- redoctober.go | 10 ++++- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/index.html b/index.html index c0fb819..4788736 100644 --- a/index.html +++ b/index.html @@ -22,13 +22,11 @@ @@ -80,31 +78,13 @@ -
-
-

Create vault/admin

-
- - -
- - -
-
- - -
- -
- -
- -

User summary / delegation list

+
+

User summary / delegation list

@@ -128,11 +108,32 @@
    +
    -
    -

    Create

    +
    - +
    +
    +

    Create vault

    + + + +
    + + +
    +
    + + +
    + + + +
    + +

    Create User

    + +
    @@ -146,11 +147,12 @@
    -
    - -
    -
    - +
    + +
    @@ -183,9 +185,7 @@
    -
    -

    Modify user

    @@ -221,7 +221,7 @@
    -
    +

    Encrypt data

    @@ -240,11 +240,17 @@
    - +
    - + +
    +
    +
    +
    + +
    @@ -260,7 +266,10 @@
    -
    +
    +
    +
    +

    Decrypt data

    @@ -284,6 +293,22 @@
    +
    +
    +
    +

    Get owners

    + +
    + + +
    + + +
    + +
    +
    +