From c46c0b7f1470aff94447a8b64a157a93b94501c6 Mon Sep 17 00:00:00 2001 From: Joachim Bauch Date: Thu, 2 Jun 2016 15:52:56 +0200 Subject: [PATCH] Reverse data in session ids. The session ids contain a Gorilla SecureCookie which internally encodes "timestamp|value". As a result, newer session ids compare "greater" than older ids. This is a problem for conferences where the participants decide based on comparison of the session id who calls who, so later participants always have to call all of the existing participants. With this change, the (random) value is at the start of the session id, resulting in "random" ordering of participants. Also some tests have been added to check session en-/decoding. --- go/channelling/tickets.go | 49 +++++++++++++++-- go/channelling/tickets_test.go | 97 ++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 go/channelling/tickets_test.go diff --git a/go/channelling/tickets.go b/go/channelling/tickets.go index 13f986b7..2f441f92 100644 --- a/go/channelling/tickets.go +++ b/go/channelling/tickets.go @@ -34,6 +34,11 @@ import ( "github.com/gorilla/securecookie" ) +var ( + // Can be set from tests to disable some log outputs. + silentOutput = false +) + type SessionValidator interface { Realm() string ValidateSession(string, string) bool @@ -77,6 +82,27 @@ func (tickets *tickets) Realm() string { return tickets.realm } +func reverseBase64String(s string) (string, error) { + decoded, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return "", err + } + + for i, j := 0, len(decoded)-1; i < j; i, j = i+1, j-1 { + decoded[i], decoded[j] = decoded[j], decoded[i] + } + return base64.URLEncoding.EncodeToString(decoded), nil +} + +func (tickets *tickets) reverseSessionId(id string) string { + reversedId, err := reverseBase64String(id) + if err != nil { + // This should never happen + panic("Could not reverse " + id) + } + return reversedId +} + func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) { var err error if token != "" { @@ -90,8 +116,11 @@ func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) { if st == nil || err != nil { sid := randomstring.NewRandomString(32) id, _ := tickets.Encode("id", sid) + id = tickets.reverseSessionId(id) st = &SessionToken{Id: id, Sid: sid} - log.Println("Created new session id", id) + if !silentOutput { + log.Println("Created new session id", id) + } } return } @@ -99,6 +128,7 @@ func (tickets *tickets) DecodeSessionToken(token string) (st *SessionToken) { func (tickets *tickets) FakeSessionToken(userid string) (st *SessionToken) { sid := fmt.Sprintf("fake-%s", randomstring.NewRandomString(27)) id, _ := tickets.Encode("id", sid) + id = tickets.reverseSessionId(id) st = &SessionToken{Id: id, Sid: sid, Userid: userid} log.Println("Created new fake session id", st.Id) return @@ -106,12 +136,23 @@ func (tickets *tickets) FakeSessionToken(userid string) (st *SessionToken) { func (tickets *tickets) ValidateSession(id, sid string) bool { var decoded string - if err := tickets.Decode("id", id, &decoded); err != nil { - log.Println("Session validation error", err, id, sid) + reversedId, err := reverseBase64String(id) + if err != nil { + if !silentOutput { + log.Println("Session format error", err, id, sid) + } + return false + } + if err := tickets.Decode("id", reversedId, &decoded); err != nil { + if !silentOutput { + log.Println("Session validation error", err, reversedId, sid) + } return false } if decoded != sid { - log.Println("Session validation failed", id, sid) + if !silentOutput { + log.Println("Session validation failed", reversedId, sid) + } return false } return true diff --git a/go/channelling/tickets_test.go b/go/channelling/tickets_test.go new file mode 100644 index 00000000..a625df01 --- /dev/null +++ b/go/channelling/tickets_test.go @@ -0,0 +1,97 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2016 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package channelling + +import ( + "crypto/rand" + "encoding/base64" + "testing" +) + +func getRandom(n int) ([]byte, error) { + result := make([]byte, n) + if _, err := rand.Read(result); err != nil { + return nil, err + } + return result, nil +} + +func Test_ReverseBase64(t *testing.T) { + for i := 0; i < 1000; i++ { + data, err := getRandom(64) + if err != nil { + t.Errorf("Could not get random data: %v", err) + continue + } + + s := base64.URLEncoding.EncodeToString(data) + reversed, err := reverseBase64String(s) + if err != nil { + t.Errorf("Could not reverse %s: %v", s, err) + continue + } + + if s == reversed { + t.Errorf("Reversing should be different for %s", s) + } + + original, err := reverseBase64String(reversed) + if err != nil { + t.Errorf("Could not reverse back %s: %v", reversed, err) + continue + } + + if s != original { + t.Errorf("Reversing back should have restored %s from %s but got %s", s, reversed, original) + } + } +} + +func Test_Sessions(t *testing.T) { + sessionSecret, err := getRandom(64) + if err != nil { + t.Fatalf("Could not create session secret: %v", err) + return + } + + encryptionSecret, err := getRandom(32) + if err != nil { + t.Fatalf("Could not create encryption secret: %v", err) + return + } + + tickets := NewTickets(sessionSecret, encryptionSecret, "test") + silentOutput = true + for i := 0; i < 1000; i++ { + st := tickets.DecodeSessionToken("") + if st == nil { + t.Error("Could not create session") + continue + } + + if !tickets.ValidateSession(st.Id, st.Sid) { + t.Errorf("Session is invalid: %v", st) + continue + } + } + silentOutput = false +}