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