handshake_ticket.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. * Copyright (c) 2015, Yawning Angel <yawning at schwanenlied dot me>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * * Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. *
  11. * * Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  19. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  20. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  21. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  22. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  23. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  24. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  25. * POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. package scramblesuit
  28. import (
  29. "bytes"
  30. "encoding/base32"
  31. "encoding/json"
  32. "errors"
  33. "fmt"
  34. "hash"
  35. "io/ioutil"
  36. "net"
  37. "os"
  38. "path"
  39. "strconv"
  40. "sync"
  41. "time"
  42. "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/csrand"
  43. )
  44. const (
  45. ticketFile = "scramblesuit_tickets.json"
  46. ticketKeyLength = 32
  47. ticketLength = 112
  48. ticketLifetime = 60 * 60 * 24 * 7
  49. ticketMinPadLength = 0
  50. ticketMaxPadLength = 1388
  51. )
  52. var (
  53. errInvalidTicket = errors.New("scramblesuit: invalid serialized ticket")
  54. )
  55. type ssTicketStore struct {
  56. sync.Mutex
  57. filePath string
  58. store map[string]*ssTicket
  59. }
  60. type ssTicket struct {
  61. key [ticketKeyLength]byte
  62. ticket [ticketLength]byte
  63. issuedAt int64
  64. }
  65. type ssTicketJSON struct {
  66. KeyTicket string `json:"key-ticket"`
  67. IssuedAt int64 `json:"issuedAt"`
  68. }
  69. func (t *ssTicket) isValid() bool {
  70. return t.issuedAt+ticketLifetime > time.Now().Unix()
  71. }
  72. func newTicket(raw []byte) (*ssTicket, error) {
  73. if len(raw) != ticketKeyLength+ticketLength {
  74. return nil, errInvalidTicket
  75. }
  76. t := &ssTicket{issuedAt: time.Now().Unix()}
  77. copy(t.key[:], raw[0:])
  78. copy(t.ticket[:], raw[ticketKeyLength:])
  79. return t, nil
  80. }
  81. func (s *ssTicketStore) storeTicket(addr net.Addr, rawT []byte) {
  82. t, err := newTicket(rawT)
  83. if err != nil {
  84. // Silently ignore ticket store failures.
  85. return
  86. }
  87. s.Lock()
  88. defer s.Unlock()
  89. // Add the ticket to the map, and checkpoint to disk. Serialization errors
  90. // are ignored because the handshake code will just use UniformDH if a
  91. // ticket is not available.
  92. s.store[addr.String()] = t
  93. _ = s.serialize()
  94. }
  95. func (s *ssTicketStore) getTicket(addr net.Addr) (*ssTicket, error) {
  96. aStr := addr.String()
  97. s.Lock()
  98. defer s.Unlock()
  99. t, ok := s.store[aStr]
  100. if ok && t != nil {
  101. // Tickets are one use only, so remove tickets from the map, and
  102. // checkpoint the map to disk.
  103. delete(s.store, aStr)
  104. err := s.serialize()
  105. if !t.isValid() {
  106. // Expired ticket, ignore it.
  107. return nil, err
  108. }
  109. return t, err
  110. }
  111. // No ticket was found, that's fine.
  112. return nil, nil
  113. }
  114. func (s *ssTicketStore) serialize() error {
  115. encMap := make(map[string]*ssTicketJSON)
  116. for k, v := range s.store {
  117. kt := make([]byte, 0, ticketKeyLength+ticketLength)
  118. kt = append(kt, v.key[:]...)
  119. kt = append(kt, v.ticket[:]...)
  120. ktStr := base32.StdEncoding.EncodeToString(kt)
  121. jsonObj := &ssTicketJSON{KeyTicket: ktStr, IssuedAt: v.issuedAt}
  122. encMap[k] = jsonObj
  123. }
  124. jsonStr, err := json.Marshal(encMap)
  125. if err != nil {
  126. return err
  127. }
  128. return ioutil.WriteFile(s.filePath, jsonStr, 0600)
  129. }
  130. func loadTicketStore(stateDir string) (*ssTicketStore, error) {
  131. fPath := path.Join(stateDir, ticketFile)
  132. s := &ssTicketStore{filePath: fPath}
  133. s.store = make(map[string]*ssTicket)
  134. f, err := ioutil.ReadFile(fPath)
  135. if err != nil {
  136. // No ticket store is fine.
  137. if os.IsNotExist(err) {
  138. return s, nil
  139. }
  140. // But a file read error is not.
  141. return nil, err
  142. }
  143. encMap := make(map[string]*ssTicketJSON)
  144. if err = json.Unmarshal(f, &encMap); err != nil {
  145. return nil, fmt.Errorf("failed to load ticket store '%s': '%s'", fPath, err)
  146. }
  147. for k, v := range encMap {
  148. raw, err := base32.StdEncoding.DecodeString(v.KeyTicket)
  149. if err != nil || len(raw) != ticketKeyLength+ticketLength {
  150. // Just silently skip corrupted tickets.
  151. continue
  152. }
  153. t := &ssTicket{issuedAt: v.IssuedAt}
  154. if !t.isValid() {
  155. // Just ignore expired tickets.
  156. continue
  157. }
  158. copy(t.key[:], raw[0:])
  159. copy(t.ticket[:], raw[ticketKeyLength:])
  160. s.store[k] = t
  161. }
  162. return s, nil
  163. }
  164. type ssTicketClientHandshake struct {
  165. mac hash.Hash
  166. ticket *ssTicket
  167. padLen int
  168. }
  169. func (hs *ssTicketClientHandshake) generateHandshake() ([]byte, error) {
  170. var buf bytes.Buffer
  171. hs.mac.Reset()
  172. // The client handshake is T | P | M | MAC(T | P | M | E)
  173. _, _ = hs.mac.Write(hs.ticket.ticket[:])
  174. m := hs.mac.Sum(nil)[:macLength]
  175. p, err := makePad(hs.padLen)
  176. if err != nil {
  177. return nil, err
  178. }
  179. // Write T, P, M.
  180. buf.Write(hs.ticket.ticket[:])
  181. buf.Write(p)
  182. buf.Write(m)
  183. // Calculate and write the MAC.
  184. e := []byte(strconv.FormatInt(getEpochHour(), 10))
  185. _, _ = hs.mac.Write(p)
  186. _, _ = hs.mac.Write(m)
  187. _, _ = hs.mac.Write(e)
  188. buf.Write(hs.mac.Sum(nil)[:macLength])
  189. hs.mac.Reset()
  190. return buf.Bytes(), nil
  191. }
  192. func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake {
  193. hs := &ssTicketClientHandshake{mac: mac, ticket: ticket}
  194. hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength)
  195. return hs
  196. }