From a19204caf4a997c5b55ed98e7e502789590fb4ce Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 26 Nov 2019 13:10:09 -0800 Subject: [PATCH] Updates given Erinns Comments --- .drone.yml | 2 +- .gitignore | 1 + applications/auth.go | 2 +- applications/token_app.go | 21 +- applications/tokenboard/client.go | 3 +- applications/tokenboard/server.go | 6 +- .../tokenboard/tokenboard_integration_test.go | 13 +- networks/tor/BaseOnionService.go | 1 - persistence/bolt_persistence.go | 1 + primitives/auditable/auditablestore.go | 2 + primitives/bloom.go | 62 ------ primitives/bloom_test.go | 24 --- primitives/core/operations.go | 186 ------------------ primitives/privacypass/dlogeq.go | 6 +- primitives/privacypass/token.go | 3 +- primitives/privacypass/token_test.go | 34 +++- primitives/privacypass/tokenserver.go | 59 ++++-- service.go | 20 +- testing/tests.sh | 12 +- 19 files changed, 138 insertions(+), 320 deletions(-) delete mode 100644 primitives/bloom.go delete mode 100644 primitives/bloom_test.go delete mode 100644 primitives/core/operations.go diff --git a/.drone.yml b/.drone.yml index a5d1d8b..496dc98 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,7 +27,7 @@ pipeline: commands: - ./tor -f ./torrc - sleep 15 - - go test -v cwtch.im/tapir/testing + - go test -race -v cwtch.im/tapir/testing notify-email: image: drillster/drone-email host: build.openprivacy.ca diff --git a/.gitignore b/.gitignore index 99c6321..e1e7b51 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage.out /applications/tor/ *.db /applications/tokenboard/tor/ +fuzzing/ diff --git a/applications/auth.go b/applications/auth.go index 0a57176..ab34d42 100644 --- a/applications/auth.go +++ b/applications/auth.go @@ -63,7 +63,7 @@ func (ea *AuthApp) Init(connection tapir.Connection) { challengeRemote, _ := json.Marshal(remoteAuthMessage) challengeLocal, _ := json.Marshal(authMessage) - // Define canonical labels so both sides of the + // Define canonical labels so both sides of the connection can generate the same key var outboundAuthMessage []byte var outboundHostname string var inboundAuthMessage []byte diff --git a/applications/token_app.go b/applications/token_app.go index 1bec236..c1f17f0 100644 --- a/applications/token_app.go +++ b/applications/token_app.go @@ -18,16 +18,16 @@ type TokenApplication struct { const HasTokensCapability = tapir.Capability("HasTokensCapability") // NewInstance should always return a new instantiation of the application. -func (powapp *TokenApplication) NewInstance() tapir.Application { +func (tokenapp *TokenApplication) NewInstance() tapir.Application { app := new(TokenApplication) - app.TokenService = powapp.TokenService + app.TokenService = tokenapp.TokenService return app } // Init is run when the connection is first started. -func (powapp *TokenApplication) Init(connection tapir.Connection) { - powapp.Transcript().NewProtocol("token-app") - log.Debugf(powapp.Transcript().OutputTranscriptToAudit()) +func (tokenapp *TokenApplication) Init(connection tapir.Connection) { + tokenapp.Transcript().NewProtocol("token-app") + log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit()) if connection.IsOutbound() { tokens, blinded := privacypass.GenerateBlindedTokenBatch(10) data, _ := json.Marshal(blinded) @@ -35,21 +35,24 @@ func (powapp *TokenApplication) Init(connection tapir.Connection) { var signedBatch privacypass.SignedBatchWithProof err := json.Unmarshal(connection.Expect(), &signedBatch) if err == nil { - verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, powapp.TokenService.Y, signedBatch.Proof, powapp.Transcript()) + verified := privacypass.UnblindSignedTokenBatch(tokens, blinded, signedBatch.SignedTokens, tokenapp.TokenService.Y, signedBatch.Proof, tokenapp.Transcript()) if verified { log.Debugf("Successfully obtained signed tokens") - powapp.Tokens = tokens + tokenapp.Tokens = tokens connection.SetCapability(HasTokensCapability) return } + // This will close the connection by default and no tokens will be available. + // This usecase can be checked by the existing WaitForCapabilityOrClose() function using the HasTokensCapability + // If the connection closes without the HasTokensCapability then the error can be handled by whatever client needs it log.Debugf("Failed to verify signed token batch") } } else { var blinded []privacypass.BlindedToken err := json.Unmarshal(connection.Expect(), &blinded) if err == nil { - batchProof := powapp.TokenService.SignBlindedTokenBatch(blinded, powapp.Transcript()) - log.Debugf(powapp.Transcript().OutputTranscriptToAudit()) + batchProof := tokenapp.TokenService.SignBlindedTokenBatch(blinded, tokenapp.Transcript()) + log.Debugf(tokenapp.Transcript().OutputTranscriptToAudit()) data, _ := json.Marshal(batchProof) connection.Send(data) return diff --git a/applications/tokenboard/client.go b/applications/tokenboard/client.go index 55fa387..ba20785 100644 --- a/applications/tokenboard/client.go +++ b/applications/tokenboard/client.go @@ -60,7 +60,6 @@ func (ta *Client) Listen() { var message Message json.Unmarshal(data, &message) - log.Debugf("Received a Message: %v", message) switch message.MessageType { case postResultMessage: log.Debugf("Post result: %x", message.PostResult.Proof) @@ -101,7 +100,7 @@ func (ta *Client) PurchaseTokens() { // Post sends a Post Request to the server func (ta *Client) Post(message auditable.Message) bool { - token, err := ta.paymentHandler.NextToken(message) + token, err := ta.paymentHandler.NextToken(message, ta.connection.Hostname()) if err == nil { data, _ := json.Marshal(Message{MessageType: postRequestMessage, PostRequest: postRequest{Token: token, Message: message}}) ta.connection.Send(data) diff --git a/applications/tokenboard/server.go b/applications/tokenboard/server.go index 764f595..c279031 100644 --- a/applications/tokenboard/server.go +++ b/applications/tokenboard/server.go @@ -1,5 +1,7 @@ package tokenboard +// NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed. + import ( "cwtch.im/tapir" "cwtch.im/tapir/applications" @@ -55,7 +57,7 @@ func (ta *Server) Listen() { var message Message json.Unmarshal(data, &message) - log.Debugf("Received a Message: %v", message) + switch message.MessageType { case postRequestMessage: postrequest := message.PostRequest @@ -77,7 +79,7 @@ func (ta *Server) Listen() { } func (ta *Server) postMessageRequest(token privacypass.SpentToken, message auditable.Message) { - if err := ta.TokenService.SpendToken(token, message); err == nil { + if err := ta.TokenService.SpendToken(token, append(message, ta.connection.ID().Hostname()...)); err == nil { log.Debugf("Token is valid") signedproof := ta.AuditableStore.Add(message) data, _ := json.Marshal(Message{MessageType: postResultMessage, PostResult: postResult{true, signedproof}}) diff --git a/applications/tokenboard/tokenboard_integration_test.go b/applications/tokenboard/tokenboard_integration_test.go index ba75cb3..a16427a 100644 --- a/applications/tokenboard/tokenboard_integration_test.go +++ b/applications/tokenboard/tokenboard_integration_test.go @@ -47,23 +47,24 @@ func (fph *FreePaymentHandler) MakePayment() { ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). ChainApplication(tokenApplication, applications.HasTokensCapability) client.Connect(fph.ServerHostname, powTokenApp) - conn,err := client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability) + conn, err := client.WaitForCapabilityOrClose(fph.ServerHostname, applications.HasTokensCapability) if err == nil { powtapp, _ := conn.App().(*applications.TokenApplication) fph.tokens = append(fph.tokens, powtapp.Tokens...) log.Debugf("Transcript: %v", powtapp.Transcript().OutputTranscriptToAudit()) conn.Close() + return } log.Debugf("Error making payment: %v", err) } -func (fph *FreePaymentHandler) NextToken(data []byte) (privacypass.SpentToken, error) { +func (fph *FreePaymentHandler) NextToken(data []byte, hostname string) (privacypass.SpentToken, error) { if len(fph.tokens) == 0 { return privacypass.SpentToken{}, errors.New("No more tokens") } token := fph.tokens[0] fph.tokens = fph.tokens[1:] - return token.SpendToken(data), nil + return token.SpendToken(append(data, hostname...)), nil } func TestTokenBoardApp(t *testing.T) { @@ -96,7 +97,7 @@ func TestTokenBoardApp(t *testing.T) { sg := new(sync.WaitGroup) sg.Add(1) go func() { - service.Listen(NewTokenBoardServer(&tokenService, serverAuditableStore)) + service.Listen(NewTokenBoardServer(tokenService, serverAuditableStore)) sg.Done() }() @@ -108,7 +109,7 @@ func TestTokenBoardApp(t *testing.T) { sg.Add(1) go func() { tokenApplication := new(applications.TokenApplication) - tokenApplication.TokenService = &tokenService + tokenApplication.TokenService = tokenService powTokenApp := new(applications.ApplicationChain). ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). ChainApplication(tokenApplication, applications.HasTokensCapability) @@ -121,7 +122,7 @@ func TestTokenBoardApp(t *testing.T) { var client tapir.Service client = new(tor.BaseOnionService) client.Init(acn, sk, &id) - client.Connect(sid.Hostname(), NewTokenBoardClient(clientAuditableStore, Handler{Store: clientAuditableStore}, &FreePaymentHandler{ACN: acn, TokenService: &tokenService, ServerHostname: spowid.Hostname()})) + client.Connect(sid.Hostname(), NewTokenBoardClient(clientAuditableStore, Handler{Store: clientAuditableStore}, &FreePaymentHandler{ACN: acn, TokenService: tokenService, ServerHostname: spowid.Hostname()})) client.WaitForCapabilityOrClose(sid.Hostname(), applications.AuthCapability) conn, _ := client.GetConnection(sid.Hostname()) tba, _ := conn.App().(*Client) diff --git a/networks/tor/BaseOnionService.go b/networks/tor/BaseOnionService.go index 6a401cb..6b2e620 100644 --- a/networks/tor/BaseOnionService.go +++ b/networks/tor/BaseOnionService.go @@ -54,7 +54,6 @@ func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name tapir.Capab func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) { var conn tapir.Connection s.connections.Range(func(key, value interface{}) bool { - log.Debugf("Checking %v", key) connection := value.(tapir.Connection) if connection.Hostname() == hostname { if !connection.IsClosed() { diff --git a/persistence/bolt_persistence.go b/persistence/bolt_persistence.go index 5cc382f..701cc55 100644 --- a/persistence/bolt_persistence.go +++ b/persistence/bolt_persistence.go @@ -53,6 +53,7 @@ func (bp *BoltPersistence) Check(bucket string, name string) (bool, error) { val = b.Get([]byte(name)) return nil }) + if err != nil { return false, err } else if val != nil { diff --git a/primitives/auditable/auditablestore.go b/primitives/auditable/auditablestore.go index ca0b52d..8a61440 100644 --- a/primitives/auditable/auditablestore.go +++ b/primitives/auditable/auditablestore.go @@ -1,5 +1,7 @@ package auditable +// WARNING NOTE: This is a sketch implementation, Not suitable for production use. The real auditable store is still being designed. + import ( "cwtch.im/tapir/persistence" "cwtch.im/tapir/primitives" diff --git a/primitives/bloom.go b/primitives/bloom.go deleted file mode 100644 index e730f85..0000000 --- a/primitives/bloom.go +++ /dev/null @@ -1,62 +0,0 @@ -package primitives - -import ( - "crypto/sha256" - "math" - "math/big" - "sync" -) - -// BloomFilter implements a bloom filter -type BloomFilter struct { - B []bool - lock sync.Mutex -} - -// Init constructs a bloom filter of size m -func (bf *BloomFilter) Init(m int64) { - bf.B = make([]bool, m) - -} - -// Hash transforms a message to a set of bit flips -func (bf *BloomFilter) Hash(msg []byte) []int { - - // Not the fastest hash function ever, but cryptographic security is more important than speed. - hash1 := sha256.Sum256(append([]byte("h1"), msg...)) - hash2 := sha256.Sum256(append([]byte("h2"), msg...)) - hash3 := sha256.Sum256(append([]byte("h3"), msg...)) - hash4 := sha256.Sum256(append([]byte("h4"), msg...)) - - m := int64(len(bf.B)) - // Number of bytes needed to pick a position from [0,m) - B := int(math.Ceil(math.Log2(float64(m)) / 8.0)) - - p1 := big.NewInt(0).SetBytes(hash1[:B]).Int64() - p2 := big.NewInt(0).SetBytes(hash2[:B]).Int64() - p3 := big.NewInt(0).SetBytes(hash3[:B]).Int64() - p4 := big.NewInt(0).SetBytes(hash4[:B]).Int64() - - return []int{int(p1), int(p2), int(p3), int(p4)} -} - -// Insert updates the BloomFilter (suitable for concurrent use) -func (bf *BloomFilter) Insert(msg []byte) { - pos := bf.Hash(msg) - bf.lock.Lock() - defer bf.lock.Unlock() - bf.B[pos[0]] = true - bf.B[pos[1]] = true - bf.B[pos[2]] = true - bf.B[pos[3]] = true -} - -// Check returns true if the messages might be in the BloomFilter -// (No false positives, possible false negatives due to the probabilistic nature of the filter) -func (bf *BloomFilter) Check(msg []byte) bool { - pos := bf.Hash(msg) - if bf.B[pos[0]] && bf.B[pos[1]] && bf.B[pos[2]] && bf.B[pos[3]] { - return true - } - return false -} diff --git a/primitives/bloom_test.go b/primitives/bloom_test.go deleted file mode 100644 index ecda1ed..0000000 --- a/primitives/bloom_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package primitives - -import ( - "strconv" - "testing" -) - -func TestBloomFilter_Insert(t *testing.T) { - bf := new(BloomFilter) - bf.Init(256) - - fp := 0 - for i := 0; i < 256; i++ { - input := []byte("test" + strconv.Itoa(256+i)) - if bf.Check(input) { - t.Log("False Positive!") - fp++ - } - bf.Insert(input) - } - - t.Logf("Num false positives %v %v%%", fp, (float64(fp)/256.0)*100) - -} diff --git a/primitives/core/operations.go b/primitives/core/operations.go deleted file mode 100644 index 61c3bdd..0000000 --- a/primitives/core/operations.go +++ /dev/null @@ -1,186 +0,0 @@ -package core - -import ( - "fmt" - ristretto "github.com/gtank/ristretto255" -) - -// ScalarVector explicit type checking -type ScalarVector []*ristretto.Scalar - -// PointVector explicit type checking -type PointVector []*ristretto.Element - -// GeneratorVector explicit type checking -type GeneratorVector []*ristretto.Element - -// CopyVector safely copies a vector -func CopyVector(G GeneratorVector) GeneratorVector { - H := make(GeneratorVector, len(G)) - for i, g := range G { - H[i] = new(ristretto.Element).Add(new(ristretto.Element).Zero(), g) - } - return H -} - -// InnerProduct takes the inner product of a and b i.e. -func InnerProduct(a, b ScalarVector) *ristretto.Scalar { - if len(a) != len(b) { - panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(b))) - } - - result := new(ristretto.Scalar).Zero() - for i, ai := range a { - result.Add(result, new(ristretto.Scalar).Multiply(ai, b[i])) - } - return result -} - -// MultiExp takes in a vector of scalars = {a,b,c...} and a vector of generator = {A,B,C...} and outputs -// {aA,bB,cC} -func MultiExp(a ScalarVector, G GeneratorVector) *ristretto.Element { - if len(a) > len(G) { - panic(fmt.Sprintf("len(a) = %v ; len(b) = %v;", len(a), len(G))) - } - result := new(ristretto.Element).Zero() - for i, ai := range a { - aG := new(ristretto.Element).ScalarMult(ai, G[i]) - result = new(ristretto.Element).Add(result, aG) - } - return result -} - -// SafeAppend is defined for a vector of Scalars -func (a ScalarVector) SafeAppend(b *ristretto.Scalar) ScalarVector { - list := make(ScalarVector, len(a)+1) - for i := 0; i < len(a); i++ { - list[i] = a[i] - } - list[len(a)] = b - return list -} - -// Join is defined for a vector of Scalars -func (a ScalarVector) Join(b ScalarVector) ScalarVector { - list := make(ScalarVector, len(a)+len(b)) - for i := 0; i < len(a); i++ { - list[i] = a[i] - } - for i := len(a); i < len(a)+len(b); i++ { - list[i] = b[i-len(a)] - } - return list -} - -// SafeAppend as defined for a vector of Generators -func (a GeneratorVector) SafeAppend(b *ristretto.Element) GeneratorVector { - list := make(GeneratorVector, len(a)+1) - for i := 0; i < len(a); i++ { - list[i] = a[i] - } - list[len(a)] = b - return list -} - -// Join as defined for a vector of Generators -func (a GeneratorVector) Join(b GeneratorVector) GeneratorVector { - list := make(GeneratorVector, len(a)+len(b)) - for i := 0; i < len(a); i++ { - list[i] = a[i] - } - for i := len(a); i < len(a)+len(b); i++ { - list[i] = b[i-len(a)] - } - return list -} - -// VectorAddScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {a+s,b+s,c+s....} -func VectorAddScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector { - result := make(ScalarVector, len(vector)) - for i := range vector { - result[i] = new(ristretto.Scalar) - result[i].Add(vector[i], scalar) - } - return result -} - -// VectorNegate takes in a vector v = {a,b,c..} and a scalar s and outputs {-a,-b,-c} -func VectorNegate(vector ScalarVector) ScalarVector { - result := make(ScalarVector, len(vector)) - for i := range vector { - result[i] = new(ristretto.Scalar).Negate(vector[i]) - } - return result -} - -// VectorMulScalar takes in a vector v = {a,b,c..} and a scalar s and outputs {as,bs,cs....} -func VectorMulScalar(vector ScalarVector, scalar *ristretto.Scalar) ScalarVector { - result := make(ScalarVector, len(vector)) - for i := range vector { - result[i] = new(ristretto.Scalar) - result[i].Multiply(vector[i], scalar) - } - return result -} - -// EntrywiseSum takes the entry wise sum of two vectors -func EntrywiseSum(vector ScalarVector, vector2 ScalarVector) ScalarVector { - result := make(ScalarVector, len(vector)) - for i, v := range vector { - result[i] = new(ristretto.Scalar) - result[i].Add(v, vector2[i]) - } - return result -} - -// EntrywiseSub takes the entry wise subtraction of two vectors -func EntrywiseSub(vector ScalarVector, vector2 ScalarVector) ScalarVector { - result := make(ScalarVector, len(vector)) - for i, v := range vector { - result[i] = new(ristretto.Scalar) - result[i].Subtract(v, vector2[i]) - } - return result -} - -// EntryWiseProduct takes the entry wise product of two vectors -func EntryWiseProduct(vector ScalarVector, vector2 ScalarVector) ScalarVector { - result := make(ScalarVector, len(vector)) - for i, v := range vector { - result[i] = new(ristretto.Scalar) - result[i].Multiply(v, vector2[i]) - } - return result -} - -// One returns a ristretto scalar == 1 -func One() *ristretto.Scalar { - one := new(ristretto.Scalar) - one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - return one -} - -// IdentityVector is a convenience function to generate a vector v = {1,1,1...1} -func IdentityVector(n int) ScalarVector { - result := make(ScalarVector, n) - one := new(ristretto.Scalar) - one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - for i := 0; i < n; i++ { - result[i] = one - } - return result -} - -// PowerVector creates a vector v = {1,x,x^2,x^3..x^n} -func PowerVector(x *ristretto.Scalar, n int) ScalarVector { - result := make(ScalarVector, n) - one := new(ristretto.Scalar) - one.Decode([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) - result[0] = one - result[1] = x - for i := 2; i < n; i++ { - result[i] = new(ristretto.Scalar) - result[i].Multiply(result[i-1], x) - } - return result -} diff --git a/primitives/privacypass/dlogeq.go b/primitives/privacypass/dlogeq.go index e4108ab..c57dafc 100644 --- a/primitives/privacypass/dlogeq.go +++ b/primitives/privacypass/dlogeq.go @@ -14,7 +14,7 @@ type DLEQProof struct { } // DiscreteLogEquivalenceProof constructs a valid DLEQProof for the given parameters and transcript -// Given P = kX, Q = kP, Y=kX +// Given Y = kX & Q = kP // Peggy: t := choose randomly from Zq // A := tX // B := tP @@ -43,12 +43,12 @@ func DiscreteLogEquivalenceProof(k *ristretto.Scalar, X *ristretto.Element, Y *r } // VerifyDiscreteLogEquivalenceProof verifies the DLEQ for the given parameters and transcript -// Given P = kX, Q = kP, Y=kX, and Proof = (c,s) +// Given Y = kX & Q = kP and Proof = (c,s) // Vicky: X' := sX // Y' := cY // P' := sP // Q' := cQ -// A' = X'+Y' == sX + cY ?= sX + ckX == (s+ck)X == tX == A +// A' = X'+Y' == sX + cY ?= sG + ckG == (s+ck)X == tX == A // B' = P'+Q' == sP + cQ ?= sP + ckP == (s+ck)P == tP == B // c' := H(transcript(X,Y,P,Q,A',B')) // Tests c ?= c diff --git a/primitives/privacypass/token.go b/primitives/privacypass/token.go index 4dc8829..04de7ae 100644 --- a/primitives/privacypass/token.go +++ b/primitives/privacypass/token.go @@ -38,7 +38,8 @@ type SpentToken struct { // TokenPaymentHandler defines an interface with external payment processors type TokenPaymentHandler interface { MakePayment() - NextToken(data []byte) (SpentToken, error) + // Next Token + NextToken(data []byte, hostname string) (SpentToken, error) } // GenBlindedToken initializes the Token diff --git a/primitives/privacypass/token_test.go b/primitives/privacypass/token_test.go index 9a23596..18b68bb 100644 --- a/primitives/privacypass/token_test.go +++ b/primitives/privacypass/token_test.go @@ -4,6 +4,8 @@ import ( "cwtch.im/tapir/persistence" "cwtch.im/tapir/primitives/core" "git.openprivacy.ca/openprivacy/libricochet-go/log" + "github.com/gtank/ristretto255" + "golang.org/x/crypto/sha3" "testing" ) @@ -31,11 +33,40 @@ func TestToken_SpendToken(t *testing.T) { } } +func TestToken_ConstrainToToken(t *testing.T) { + server := NewTokenServer() + + token := new(Token) + blindedToken := token.GenBlindedToken() + + signedToken := server.SignBlindedToken(blindedToken) + token.unblindSignedToken(signedToken) + + spentToken := token.SpendToken([]byte("Hello")) + + if server.SpendToken(spentToken, []byte("Hello World")) == nil { + t.Errorf("Token Should be InValid") + } + + token2 := new(Token) + blindedToken2 := token2.GenBlindedToken() + Ht := sha3.Sum512(token.t) + T := new(ristretto255.Element).FromUniformBytes(Ht[:]) + // Constraint forces T = kW to be part of the batch proof + // And because the batch proof must prove that *all* inputs share the same key and also checks the servers public key + // We get a consistency check for almost free. + signedTokens := server.SignBlindedTokenBatchWithConstraint([]BlindedToken{blindedToken2}, *token, core.NewTranscript("")) + transcript := core.NewTranscript("") + t.Logf("Result of constaint proof %v", UnblindSignedTokenBatch([]*Token{token2}, []BlindedToken{blindedToken2, {P: T}}, append(signedTokens.SignedTokens, SignedToken{token.W}), server.Y, signedTokens.Proof, transcript)) + t.Log(transcript.OutputTranscriptToAudit()) +} + func TestGenerateBlindedTokenBatch(t *testing.T) { log.SetLevel(log.LevelDebug) db := new(persistence.BoltPersistence) db.Open("tokens.db") - server := NewTokenServer() + defer db.Close() + server := NewTokenServerFromStore(db) clientTranscript := core.NewTranscript("privacyPass") serverTranscript := core.NewTranscript("privacyPass") @@ -65,5 +96,4 @@ func TestGenerateBlindedTokenBatch(t *testing.T) { if verified { t.Errorf("Something went wrong, the proof passed with wrong transcript: %s", wrongTranscript.OutputTranscriptToAudit()) } - db.Close() } diff --git a/primitives/privacypass/tokenserver.go b/primitives/privacypass/tokenserver.go index 053400e..a4f32a7 100644 --- a/primitives/privacypass/tokenserver.go +++ b/primitives/privacypass/tokenserver.go @@ -23,29 +23,48 @@ type TokenServer struct { // SignedBatchWithProof encapsulates a signed batch of blinded tokens with a batch proof for verification type SignedBatchWithProof struct { - SignedTokens []SignedToken - Proof DLEQProof + SignedTokens []SignedToken `json:"st"` + Proof DLEQProof `json:"dp"` } const tokenBucket = "tokens" +const keyBucket = "keys" // NewTokenServer generates a new TokenServer (used mostly for testing with ephemeral instances) -func NewTokenServer() TokenServer { +func NewTokenServer() *TokenServer { k := new(ristretto.Scalar) b := make([]byte, 64) - rand.Read(b) + _, err := rand.Read(b) + if err != nil { + // unable to generate secure random numbers + panic("unable to generate secure random numbers") + } k.FromUniformBytes(b) - return TokenServer{k, new(ristretto.Element).ScalarBaseMult(k), make(map[string]bool), nil, sync.Mutex{}} + return &TokenServer{k, new(ristretto.Element).ScalarBaseMult(k), make(map[string]bool), nil, sync.Mutex{}} } // NewTokenServerFromStore generates a new TokenServer backed by a persistence service. -func NewTokenServerFromStore(persistenceService persistence.Service) TokenServer { - k := new(ristretto.Scalar) - b := make([]byte, 64) - rand.Read(b) - k.FromUniformBytes(b) +func NewTokenServerFromStore(persistenceService persistence.Service) *TokenServer { + tokenServer := NewTokenServer() persistenceService.Setup([]string{tokenBucket}) - return TokenServer{k, new(ristretto.Element).ScalarBaseMult(k), make(map[string]bool), persistenceService, sync.Mutex{}} + persistenceService.Setup([]string{keyBucket}) + exists, err := persistenceService.Check(keyBucket, "k") + if err != nil { + panic(err) + } + // if we don't have a stored k then save the one we have generated + // otherwise use the k we have stored + if !exists { + persistenceService.Persist(keyBucket, "k", tokenServer.k) + } else { + persistenceService.Load(keyBucket, "k", tokenServer.k) + + // recalculate public key from stored k + tokenServer.Y = new(ristretto.Element).ScalarBaseMult(tokenServer.k) + } + + tokenServer.persistanceService = persistenceService + return tokenServer } // SignBlindedToken calculates kP for the given BlindedToken P @@ -63,6 +82,20 @@ func (ts *TokenServer) SignBlindedTokenBatch(blindedTokens []BlindedToken, trans return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, signedTokens, transcript)} } +// SignBlindedTokenBatchWithConstraint signs a batch of blinded tokens under a given transcript given a contraint that the tokens must be signed +// by the same public key as an existing token +func (ts *TokenServer) SignBlindedTokenBatchWithConstraint(blindedTokens []BlindedToken, token Token, transcript *core.Transcript) SignedBatchWithProof { + var signedTokens []SignedToken + for _, bt := range blindedTokens { + signedTokens = append(signedTokens, ts.SignBlindedToken(bt)) + } + Ht := sha3.Sum512(token.t) + T := new(ristretto.Element).FromUniformBytes(Ht[:]) + // W == kT + blindedTokens = append(blindedTokens, BlindedToken{P: T}) + return SignedBatchWithProof{signedTokens, ts.constructBatchProof(blindedTokens, append(signedTokens, SignedToken{Q: token.W}), transcript)} +} + // constructBatchProof construct a batch proof that all the signed tokens have been signed correctly func (ts *TokenServer) constructBatchProof(blindedTokens []BlindedToken, signedTokens []SignedToken, transcript *core.Transcript) DLEQProof { transcript.NewProtocol(BatchProofProtocol) @@ -102,8 +135,8 @@ func (ts *TokenServer) SpendToken(token SpentToken, data []byte) error { W := new(ristretto.Element).ScalarMult(ts.k, T) key := sha3.Sum256(append(token.T, W.Encode(nil)...)) mac := hmac.New(sha3.New512, key[:]) - K := mac.Sum(data) - result := hmac.Equal(token.MAC, K) + computedMAC := mac.Sum(data) + result := hmac.Equal(token.MAC, computedMAC) if result == true { if ts.persistanceService == nil { ts.seen[hex.EncodeToString(token.T)] = true diff --git a/service.go b/service.go index 72645ee..bc2a6bd 100644 --- a/service.go +++ b/service.go @@ -51,6 +51,7 @@ type connection struct { outbound bool closed bool MaxLength int + lock sync.Mutex } // NewConnection creates a new Connection @@ -73,17 +74,23 @@ func (c *connection) ID() *primitives.Identity { // App returns the overarching application using this Connection. func (c *connection) App() Application { + c.lock.Lock() + defer c.lock.Unlock() return c.app } // App returns the overarching application using this Connection. func (c *connection) SetApp(application Application) { + c.lock.Lock() + defer c.lock.Unlock() c.app = application } // Hostname returns the hostname of the connection (if the connection has not been authorized it will return the // temporary hostname identifier) func (c *connection) Hostname() string { + c.lock.Lock() + defer c.lock.Unlock() return c.hostname } @@ -95,11 +102,15 @@ func (c *connection) IsOutbound() bool { // IsClosed returns true if the connection is closed (connections cannot be reopened) func (c *connection) IsClosed() bool { + c.lock.Lock() + defer c.lock.Unlock() return c.closed } // SetHostname sets the hostname on the connection func (c *connection) SetHostname(hostname string) { + c.lock.Lock() + defer c.lock.Unlock() log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.identity.Hostname(), c.hostname, hostname) c.hostname = hostname } @@ -118,6 +129,8 @@ func (c *connection) HasCapability(name Capability) bool { // Close forcibly closes the connection func (c *connection) Close() { + c.lock.Lock() + defer c.lock.Unlock() c.closed = true c.conn.Close() } @@ -133,7 +146,8 @@ func (c *connection) Expect() []byte { c.closed = true return []byte{} } - + c.lock.Lock() + defer c.lock.Unlock() if c.encrypted { var decryptNonce [24]byte copy(decryptNonce[:], buffer[:24]) @@ -157,6 +171,8 @@ func (c *connection) Expect() []byte { // SetEncryptionKey turns on application-level encryption on the connection using the given key. func (c *connection) SetEncryptionKey(key [32]byte) { + c.lock.Lock() + defer c.lock.Unlock() c.key = key c.encrypted = true } @@ -168,6 +184,8 @@ func (c *connection) Send(message []byte) { binary.PutUvarint(buffer[0:2], uint64(len(message))) copy(buffer[2:], message) + c.lock.Lock() + defer c.lock.Unlock() if c.encrypted { var nonce [24]byte if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { diff --git a/testing/tests.sh b/testing/tests.sh index 3f79df6..f402dbe 100755 --- a/testing/tests.sh +++ b/testing/tests.sh @@ -2,12 +2,12 @@ set -e pwd -go test ${1} -coverprofile=applications.cover.out -v ./applications -go test ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard -go test ${1} -coverprofile=primitives.cover.out -v ./primitives -go test ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable -go test ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core -go test ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass +go test -race ${1} -coverprofile=applications.cover.out -v ./applications +go test -race ${1} -coverprofile=applications.tokenboard.cover.out -v ./applications/tokenboard +go test -race ${1} -coverprofile=primitives.cover.out -v ./primitives +go test -race ${1} -coverprofile=primitives.auditable.cover.out -v ./primitives/auditable +go test -race ${1} -coverprofile=primitives.core.cover.out -v ./primitives/core +go test -race ${1} -coverprofile=primitives.privacypass.cover.out -v ./primitives/privacypass go test -bench "BenchmarkAuditableStore" -benchtime 1000x primitives/auditable/*.go echo "mode: set" > coverage.out && cat *.cover.out | grep -v mode: | sort -r | \ awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out