values.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Copyright (c) 2019, 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. /*
  20. Package values provides a mechanism for specifying and selecting dynamic
  21. values employed by the Psiphon client and server.
  22. */
  23. package values
  24. import (
  25. "bytes"
  26. "encoding/gob"
  27. "fmt"
  28. "regexp/syntax"
  29. "strings"
  30. "sync"
  31. "sync/atomic"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  34. regen "github.com/zach-klippenstein/goregen"
  35. "golang.org/x/crypto/nacl/secretbox"
  36. )
  37. // ValueSpec specifies a value selection space.
  38. type ValueSpec struct {
  39. Probability float64
  40. Parts []PartSpec
  41. Padding []byte
  42. }
  43. type PartSpec struct {
  44. Items []string
  45. MinCount, MaxCount int
  46. }
  47. // NewPickOneSpec creates a simple spec to select one item from a list as a
  48. // value.
  49. func NewPickOneSpec(items []string) *ValueSpec {
  50. return &ValueSpec{
  51. Probability: 1.0,
  52. Parts: []PartSpec{{Items: items, MinCount: 1, MaxCount: 1}},
  53. }
  54. }
  55. // GetValue selects a value according to the spec. An optional seed may
  56. // be specified to support replay.
  57. func (spec *ValueSpec) GetValue(PRNG *prng.PRNG) string {
  58. rangeFunc := prng.Range
  59. intnFunc := prng.Intn
  60. if PRNG != nil {
  61. rangeFunc = PRNG.Range
  62. intnFunc = PRNG.Intn
  63. }
  64. var value strings.Builder
  65. for _, part := range spec.Parts {
  66. count := rangeFunc(part.MinCount, part.MaxCount)
  67. for i := 0; i < count; i++ {
  68. value.WriteString(part.Items[intnFunc(len(part.Items))])
  69. }
  70. }
  71. return value.String()
  72. }
  73. // Obfuscate creates an obfuscated blob from a spec.
  74. func (spec *ValueSpec) Obfuscate(
  75. obfuscationKey []byte,
  76. minPadding, maxPadding int) ([]byte, error) {
  77. if len(obfuscationKey) != 32 {
  78. return nil, errors.TraceNew("invalid key length")
  79. }
  80. var key [32]byte
  81. copy(key[:], []byte(obfuscationKey))
  82. spec.Padding = prng.Padding(minPadding, maxPadding)
  83. var obfuscatedValueSpec bytes.Buffer
  84. err := gob.NewEncoder(&obfuscatedValueSpec).Encode(spec)
  85. if err != nil {
  86. return nil, errors.Trace(err)
  87. }
  88. return secretbox.Seal(
  89. nil, []byte(obfuscatedValueSpec.Bytes()), &[24]byte{}, &key), nil
  90. }
  91. // DeobfuscateValueSpec reconstitutes an obfuscated spec.
  92. func DeobfuscateValueSpec(obfuscatedValueSpec, obfuscationKey []byte) *ValueSpec {
  93. if len(obfuscationKey) != 32 {
  94. return nil
  95. }
  96. var key [32]byte
  97. copy(key[:], obfuscationKey)
  98. deobfuscatedValueSpec, ok := secretbox.Open(nil, obfuscatedValueSpec, &[24]byte{}, &key)
  99. if !ok {
  100. return nil
  101. }
  102. spec := new(ValueSpec)
  103. err := gob.NewDecoder(bytes.NewBuffer(deobfuscatedValueSpec)).Decode(spec)
  104. if err != nil {
  105. return nil
  106. }
  107. spec.Padding = nil
  108. return spec
  109. }
  110. var (
  111. revision atomic.Value
  112. sshClientVersionsSpec atomic.Value
  113. sshServerVersionsSpec atomic.Value
  114. userAgentsSpec atomic.Value
  115. hostNamesSpec atomic.Value
  116. cookieNamesSpec atomic.Value
  117. contentTypeSpec atomic.Value
  118. )
  119. // SetRevision set the revision value, which may be used to track which value
  120. // specs are active. The revision is not managed by this package and must be
  121. // set by the package user.
  122. func SetRevision(rev string) {
  123. revision.Store(rev)
  124. }
  125. // GetRevision gets the previously set revision.
  126. func GetRevision() string {
  127. rev, ok := revision.Load().(string)
  128. if !ok {
  129. return "none"
  130. }
  131. return rev
  132. }
  133. // SetSSHClientVersionsSpec sets the corresponding value spec.
  134. func SetSSHClientVersionsSpec(spec *ValueSpec) {
  135. if spec == nil {
  136. return
  137. }
  138. sshClientVersionsSpec.Store(spec)
  139. }
  140. // GetSSHClientVersion selects a value based on the previously set spec, or
  141. // returns a default when no spec is set.
  142. func GetSSHClientVersion() string {
  143. spec, ok := sshClientVersionsSpec.Load().(*ValueSpec)
  144. if !ok || !prng.FlipWeightedCoin(spec.Probability) {
  145. return generate(prng.DefaultPRNG(), "SSH-2\\.0-OpenSSH_[7-8]\\.[0-9]")
  146. }
  147. return spec.GetValue(nil)
  148. }
  149. // SetSSHServerVersionsSpec sets the corresponding value spec.
  150. func SetSSHServerVersionsSpec(spec *ValueSpec) {
  151. if spec == nil {
  152. return
  153. }
  154. sshServerVersionsSpec.Store(spec)
  155. }
  156. // GetSSHServerVersion selects a value based on the previously set spec, or
  157. // returns a default when no spec is set.
  158. func GetSSHServerVersion(seed *prng.Seed) string {
  159. var PRNG *prng.PRNG
  160. if seed != nil {
  161. PRNG = prng.NewPRNGWithSeed(seed)
  162. }
  163. spec, ok := sshServerVersionsSpec.Load().(*ValueSpec)
  164. if !ok || !PRNG.FlipWeightedCoin(spec.Probability) {
  165. return generate(PRNG, "SSH-2\\.0-OpenSSH_[7-8]\\.[0-9]")
  166. }
  167. return spec.GetValue(PRNG)
  168. }
  169. // SetUserAgentsSpec sets the corresponding value spec.
  170. func SetUserAgentsSpec(spec *ValueSpec) {
  171. if spec == nil {
  172. return
  173. }
  174. userAgentsSpec.Store(spec)
  175. }
  176. // GetUserAgent selects a value based on the previously set spec, or
  177. // returns a default when no spec is set.
  178. func GetUserAgent() string {
  179. spec, ok := userAgentsSpec.Load().(*ValueSpec)
  180. if !ok || !prng.FlipWeightedCoin(spec.Probability) {
  181. return generateUserAgent()
  182. }
  183. return spec.GetValue(nil)
  184. }
  185. // SetHostNamesSpec sets the corresponding value spec.
  186. func SetHostNamesSpec(spec *ValueSpec) {
  187. if spec == nil {
  188. return
  189. }
  190. hostNamesSpec.Store(spec)
  191. }
  192. // GetHostName selects a value based on the previously set spec, or
  193. // returns a default when no spec is set.
  194. func GetHostName() string {
  195. spec, ok := hostNamesSpec.Load().(*ValueSpec)
  196. if !ok || !prng.FlipWeightedCoin(spec.Probability) {
  197. return generate(prng.DefaultPRNG(), "[a-z]{4,15}(\\.com|\\.net|\\.org)")
  198. }
  199. return spec.GetValue(nil)
  200. }
  201. // SetCookieNamesSpec sets the corresponding value spec.
  202. func SetCookieNamesSpec(spec *ValueSpec) {
  203. if spec == nil {
  204. return
  205. }
  206. cookieNamesSpec.Store(spec)
  207. }
  208. // GetCookieName selects a value based on the previously set spec, or
  209. // returns a default when no spec is set.
  210. func GetCookieName(PRNG *prng.PRNG) string {
  211. spec, ok := cookieNamesSpec.Load().(*ValueSpec)
  212. if !ok || !PRNG.FlipWeightedCoin(spec.Probability) {
  213. return generate(PRNG, "[a-z_]{2,10}")
  214. }
  215. return spec.GetValue(PRNG)
  216. }
  217. // SetContentTypesSpec sets the corresponding value spec.
  218. func SetContentTypesSpec(spec *ValueSpec) {
  219. if spec == nil {
  220. return
  221. }
  222. contentTypeSpec.Store(spec)
  223. }
  224. // GetContentType selects a value based on the previously set spec, or
  225. // returns a default when no spec is set.
  226. func GetContentType(PRNG *prng.PRNG) string {
  227. spec, ok := contentTypeSpec.Load().(*ValueSpec)
  228. if !ok || !PRNG.FlipWeightedCoin(spec.Probability) {
  229. return generate(PRNG, "application/octet-stream|audio/mpeg|image/jpeg|video/mpeg")
  230. }
  231. return spec.GetValue(PRNG)
  232. }
  233. func generate(PRNG *prng.PRNG, pattern string) string {
  234. args := &regen.GeneratorArgs{
  235. RngSource: PRNG,
  236. Flags: syntax.OneLine | syntax.NonGreedy,
  237. }
  238. rg, err := regen.NewGenerator(pattern, args)
  239. if err != nil {
  240. panic(err.Error())
  241. }
  242. return rg.Generate()
  243. }
  244. var (
  245. userAgentGeneratorMutex sync.Mutex
  246. userAgentGenerators []*userAgentGenerator
  247. )
  248. type userAgentGenerator struct {
  249. version func() string
  250. generator regen.Generator
  251. }
  252. func generateUserAgent() string {
  253. userAgentGeneratorMutex.Lock()
  254. defer userAgentGeneratorMutex.Unlock()
  255. if userAgentGenerators == nil {
  256. // Initialize user agent generators once and reuse. This saves the
  257. // overhead of parsing the relatively complex regular expressions on
  258. // each GetUserAgent call.
  259. // These regular expressions and version ranges are adapted from:
  260. //
  261. // https://github.com/tarampampam/random-user-agent/blob/d0dd4059ac518e8b0f79510d050877c685539fbc/src/useragent/generator.ts
  262. // https://github.com/tarampampam/random-user-agent/blob/d0dd4059ac518e8b0f79510d050877c685539fbc/src/useragent/versions.ts
  263. chromeVersion := func() string {
  264. return fmt.Sprintf("%d.0.%d.%d",
  265. prng.Range(101, 104), prng.Range(4951, 5162), prng.Range(80, 212))
  266. }
  267. safariVersion := func() string {
  268. return fmt.Sprintf("%d.%d.%d",
  269. prng.Range(537, 611), prng.Range(1, 36), prng.Range(1, 15))
  270. }
  271. makeGenerator := func(pattern string) regen.Generator {
  272. args := &regen.GeneratorArgs{
  273. RngSource: prng.DefaultPRNG(),
  274. Flags: syntax.OneLine | syntax.NonGreedy,
  275. }
  276. rg, err := regen.NewGenerator(pattern, args)
  277. if err != nil {
  278. panic(err.Error())
  279. }
  280. return rg
  281. }
  282. userAgentGenerators = []*userAgentGenerator{
  283. &userAgentGenerator{chromeVersion, makeGenerator("Mozilla/5\\.0 \\(Macintosh; Intel Mac OS X 1[01]_(1|)[0-5]\\) AppleWebKit/537\\.36 \\(KHTML, like Gecko\\) Chrome/__VER__ Safari/537\\.36")},
  284. &userAgentGenerator{chromeVersion, makeGenerator("Mozilla/5\\.0 \\(Windows NT 1(0|0|1)\\.0; (WOW64|Win64)(; x64|; x64|)\\) AppleWebKit/537\\.36 \\(KHTML, like Gecko\\) Chrome/__VER__ Safari/537\\.36")},
  285. &userAgentGenerator{chromeVersion, makeGenerator("Mozilla/5\\.0 \\(Linux; Android (9|10|10|11|12); [a-zA-Z0-9_]{5,10}\\) AppleWebKit/537\\.36 \\(KHTML, like Gecko\\) Chrome/__VER__ Mobile Safari/537\\.36")},
  286. &userAgentGenerator{safariVersion, makeGenerator("Mozilla/5\\.0 \\(iPhone; CPU iPhone OS 1[3-5]_[1-5] like Mac OS X\\) AppleWebKit/(__VER__|__VER__|600\\.[1-8]\\.[12][0-7]|537\\.36) \\(KHTML, like Gecko\\) Version/1[0-4]\\.[0-7](\\.[1-9][0-7]|) Mobile/[A-Z0-9]{6} Safari/__VER__")},
  287. &userAgentGenerator{safariVersion, makeGenerator("Mozilla/5\\.0 \\(Macintosh; Intel Mac OS X 1[01]_(1|)[0-7](_[1-7]|)\\) AppleWebKit/(__VER__|__VER__|600\\.[1-8]\\.[12][0-7]|537\\.36) \\(KHTML, like Gecko\\) Version/1[0-4]\\.[0-7](\\.[1-9][0-7]|) Safari/__VER__")},
  288. }
  289. }
  290. g := userAgentGenerators[prng.Range(0, len(userAgentGenerators)-1)]
  291. value := g.generator.Generate()
  292. value = strings.ReplaceAll(value, "__VER__", g.version())
  293. return value
  294. }