123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- /*
- * Copyright (c) 2015, Yawning Angel <yawning at schwanenlied dot me>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
- package scramblesuit
- import (
- "bytes"
- "encoding/base32"
- "encoding/json"
- "errors"
- "fmt"
- "hash"
- "io/ioutil"
- "net"
- "os"
- "path"
- "strconv"
- "sync"
- "time"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/csrand"
- )
- const (
- ticketFile = "scramblesuit_tickets.json"
- ticketKeyLength = 32
- ticketLength = 112
- ticketLifetime = 60 * 60 * 24 * 7
- ticketMinPadLength = 0
- ticketMaxPadLength = 1388
- )
- var (
- errInvalidTicket = errors.New("scramblesuit: invalid serialized ticket")
- )
- type ssTicketStore struct {
- sync.Mutex
- filePath string
- store map[string]*ssTicket
- }
- type ssTicket struct {
- key [ticketKeyLength]byte
- ticket [ticketLength]byte
- issuedAt int64
- }
- type ssTicketJSON struct {
- KeyTicket string `json:"key-ticket"`
- IssuedAt int64 `json:"issuedAt"`
- }
- func (t *ssTicket) isValid() bool {
- return t.issuedAt+ticketLifetime > time.Now().Unix()
- }
- func newTicket(raw []byte) (*ssTicket, error) {
- if len(raw) != ticketKeyLength+ticketLength {
- return nil, errInvalidTicket
- }
- t := &ssTicket{issuedAt: time.Now().Unix()}
- copy(t.key[:], raw[0:])
- copy(t.ticket[:], raw[ticketKeyLength:])
- return t, nil
- }
- func (s *ssTicketStore) storeTicket(addr net.Addr, rawT []byte) {
- t, err := newTicket(rawT)
- if err != nil {
- // Silently ignore ticket store failures.
- return
- }
- s.Lock()
- defer s.Unlock()
- // Add the ticket to the map, and checkpoint to disk. Serialization errors
- // are ignored because the handshake code will just use UniformDH if a
- // ticket is not available.
- s.store[addr.String()] = t
- _ = s.serialize()
- }
- func (s *ssTicketStore) getTicket(addr net.Addr) (*ssTicket, error) {
- aStr := addr.String()
- s.Lock()
- defer s.Unlock()
- t, ok := s.store[aStr]
- if ok && t != nil {
- // Tickets are one use only, so remove tickets from the map, and
- // checkpoint the map to disk.
- delete(s.store, aStr)
- err := s.serialize()
- if !t.isValid() {
- // Expired ticket, ignore it.
- return nil, err
- }
- return t, err
- }
- // No ticket was found, that's fine.
- return nil, nil
- }
- func (s *ssTicketStore) serialize() error {
- encMap := make(map[string]*ssTicketJSON)
- for k, v := range s.store {
- kt := make([]byte, 0, ticketKeyLength+ticketLength)
- kt = append(kt, v.key[:]...)
- kt = append(kt, v.ticket[:]...)
- ktStr := base32.StdEncoding.EncodeToString(kt)
- jsonObj := &ssTicketJSON{KeyTicket: ktStr, IssuedAt: v.issuedAt}
- encMap[k] = jsonObj
- }
- jsonStr, err := json.Marshal(encMap)
- if err != nil {
- return err
- }
- return ioutil.WriteFile(s.filePath, jsonStr, 0600)
- }
- func loadTicketStore(stateDir string) (*ssTicketStore, error) {
- fPath := path.Join(stateDir, ticketFile)
- s := &ssTicketStore{filePath: fPath}
- s.store = make(map[string]*ssTicket)
- f, err := ioutil.ReadFile(fPath)
- if err != nil {
- // No ticket store is fine.
- if os.IsNotExist(err) {
- return s, nil
- }
- // But a file read error is not.
- return nil, err
- }
- encMap := make(map[string]*ssTicketJSON)
- if err = json.Unmarshal(f, &encMap); err != nil {
- return nil, fmt.Errorf("failed to load ticket store '%s': '%s'", fPath, err)
- }
- for k, v := range encMap {
- raw, err := base32.StdEncoding.DecodeString(v.KeyTicket)
- if err != nil || len(raw) != ticketKeyLength+ticketLength {
- // Just silently skip corrupted tickets.
- continue
- }
- t := &ssTicket{issuedAt: v.IssuedAt}
- if !t.isValid() {
- // Just ignore expired tickets.
- continue
- }
- copy(t.key[:], raw[0:])
- copy(t.ticket[:], raw[ticketKeyLength:])
- s.store[k] = t
- }
- return s, nil
- }
- type ssTicketClientHandshake struct {
- mac hash.Hash
- ticket *ssTicket
- padLen int
- }
- func (hs *ssTicketClientHandshake) generateHandshake() ([]byte, error) {
- var buf bytes.Buffer
- hs.mac.Reset()
- // The client handshake is T | P | M | MAC(T | P | M | E)
- _, _ = hs.mac.Write(hs.ticket.ticket[:])
- m := hs.mac.Sum(nil)[:macLength]
- p, err := makePad(hs.padLen)
- if err != nil {
- return nil, err
- }
- // Write T, P, M.
- buf.Write(hs.ticket.ticket[:])
- buf.Write(p)
- buf.Write(m)
- // Calculate and write the MAC.
- e := []byte(strconv.FormatInt(getEpochHour(), 10))
- _, _ = hs.mac.Write(p)
- _, _ = hs.mac.Write(m)
- _, _ = hs.mac.Write(e)
- buf.Write(hs.mac.Sum(nil)[:macLength])
- hs.mac.Reset()
- return buf.Bytes(), nil
- }
- func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake {
- hs := &ssTicketClientHandshake{mac: mac, ticket: ticket}
- hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength)
- return hs
- }
|