passthrough.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. * Copyright (c) 2020, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package obfuscator
  20. import (
  21. "crypto/hmac"
  22. "crypto/rand"
  23. "crypto/sha256"
  24. "crypto/subtle"
  25. "encoding/binary"
  26. "io"
  27. "time"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  29. "golang.org/x/crypto/hkdf"
  30. )
  31. const (
  32. TLS_PASSTHROUGH_NONCE_SIZE = 16
  33. TLS_PASSTHROUGH_KEY_SIZE = 32
  34. TLS_PASSTHROUGH_TIME_PERIOD = 20 * time.Minute
  35. TLS_PASSTHROUGH_HISTORY_TTL = TLS_PASSTHROUGH_TIME_PERIOD * 3
  36. TLS_PASSTHROUGH_MESSAGE_SIZE = 32
  37. )
  38. // MakeTLSPassthroughMessage generates a unique TLS passthrough message
  39. // using the passthrough key derived from a master obfuscated key.
  40. //
  41. // The passthrough message demonstrates knowledge of the obfuscated key.
  42. // When useTimeFactor is set, the message will also reflect the current
  43. // time period, limiting how long it remains valid.
  44. //
  45. // The configurable useTimeFactor enables support for legacy clients and
  46. // servers which don't use the time factor.
  47. func MakeTLSPassthroughMessage(
  48. useTimeFactor bool, obfuscatedKey string) ([]byte, error) {
  49. passthroughKey, err := derivePassthroughKey(useTimeFactor, 0, obfuscatedKey)
  50. if err != nil {
  51. return nil, errors.Trace(err)
  52. }
  53. message := make([]byte, TLS_PASSTHROUGH_MESSAGE_SIZE)
  54. _, err = rand.Read(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
  55. if err != nil {
  56. return nil, errors.Trace(err)
  57. }
  58. h := hmac.New(sha256.New, passthroughKey)
  59. h.Write(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
  60. copy(message[TLS_PASSTHROUGH_NONCE_SIZE:], h.Sum(nil))
  61. return message, nil
  62. }
  63. // VerifyTLSPassthroughMessage checks that the specified passthrough message
  64. // was generated using the passthrough key.
  65. //
  66. // useTimeFactor must be set to the same value used in
  67. // MakeTLSPassthroughMessage.
  68. func VerifyTLSPassthroughMessage(
  69. useTimeFactor bool, obfuscatedKey string, message []byte) bool {
  70. // If the message is the wrong length, continue processing with a stub
  71. // message of the correct length. This is to avoid leaking the existence of
  72. // passthrough via timing differences.
  73. if len(message) != TLS_PASSTHROUGH_MESSAGE_SIZE {
  74. var stub [TLS_PASSTHROUGH_MESSAGE_SIZE]byte
  75. message = stub[:]
  76. }
  77. if useTimeFactor {
  78. // Check three rounded time periods: the current one, the previous
  79. // one, and the future one. Even if the client clock is ahead of the
  80. // server clock by only a short amount, it can use the future time
  81. // period, from the server's perspective, when the server's clock is
  82. // close to the end of its current time period. And even if the
  83. // client and server clocks are perfectly synchronized, the client
  84. // may use the previous time period and then time advances to the
  85. // next time period by the time the server receives the message.
  86. //
  87. // All three time periods are always checked, to avoid leaking via
  88. // timing differences.
  89. match := false
  90. for _, timePeriodShift := range []int64{-1, 0, 1} {
  91. passthroughKey, err := derivePassthroughKey(
  92. useTimeFactor, timePeriodShift, obfuscatedKey)
  93. if err != nil {
  94. // derivePassthroughKey is not expected to fail.
  95. // TODO: log error
  96. return false
  97. }
  98. h := hmac.New(sha256.New, passthroughKey)
  99. h.Write(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
  100. if 1 == subtle.ConstantTimeCompare(
  101. message[TLS_PASSTHROUGH_NONCE_SIZE:],
  102. h.Sum(nil)[0:TLS_PASSTHROUGH_MESSAGE_SIZE-TLS_PASSTHROUGH_NONCE_SIZE]) {
  103. match = true
  104. }
  105. }
  106. return match
  107. }
  108. passthroughKey, err := derivePassthroughKey(false, 0, obfuscatedKey)
  109. if err != nil {
  110. return false
  111. }
  112. h := hmac.New(sha256.New, passthroughKey)
  113. h.Write(message[0:TLS_PASSTHROUGH_NONCE_SIZE])
  114. return 1 == subtle.ConstantTimeCompare(
  115. message[TLS_PASSTHROUGH_NONCE_SIZE:],
  116. h.Sum(nil)[0:TLS_PASSTHROUGH_MESSAGE_SIZE-TLS_PASSTHROUGH_NONCE_SIZE])
  117. }
  118. // timePeriodSeconds is variable, to enable overriding the value in
  119. // TestTLSPassthrough. This value should not be overridden outside of test
  120. // cases.
  121. var timePeriodSeconds = int64(TLS_PASSTHROUGH_TIME_PERIOD / time.Second)
  122. func derivePassthroughKey(
  123. useTimeFactor bool, timePeriodShift int64, obfuscatedKey string) ([]byte, error) {
  124. secret := []byte(obfuscatedKey)
  125. salt := []byte("passthrough-obfuscation-key")
  126. if useTimeFactor {
  127. // Include a time factor, so messages created with this key remain valid
  128. // only for a limited time period. The current time is rounded, allowing the
  129. // client clock to be slightly ahead of or behind of the server clock.
  130. //
  131. // This time factor mechanism is used in concert with SeedHistory to detect
  132. // passthrough message replay. SeedHistory, a history of recent passthrough
  133. // messages, is used to detect duplicate passthrough messages. The time
  134. // factor bounds the necessary history length: passthrough messages older
  135. // than the time period no longer need to be retained in history.
  136. //
  137. // We _always_ derive the passthrough key for each
  138. // MakeTLSPassthroughMessage, even for multiple calls in the same time
  139. // factor period, to avoid leaking the presense of passthough via timing
  140. // differences at time boundaries. We assume that the server always or never
  141. // sets useTimeFactor.
  142. roundedTimePeriod := (time.Now().Unix() +
  143. (timePeriodSeconds / 2) +
  144. timePeriodSeconds*timePeriodShift) / timePeriodSeconds
  145. var timeFactor [8]byte
  146. binary.LittleEndian.PutUint64(timeFactor[:], uint64(roundedTimePeriod))
  147. salt = append(salt, timeFactor[:]...)
  148. }
  149. key := make([]byte, TLS_PASSTHROUGH_KEY_SIZE)
  150. _, err := io.ReadFull(hkdf.New(sha256.New, secret, salt, nil), key)
  151. if err != nil {
  152. return nil, errors.Trace(err)
  153. }
  154. return key, nil
  155. }