osl_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /*
  2. * Copyright (c) 2016, 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 osl
  20. import (
  21. "bytes"
  22. "encoding/base64"
  23. "encoding/hex"
  24. "fmt"
  25. "io/ioutil"
  26. "net"
  27. "testing"
  28. "time"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  30. )
  31. func TestOSL(t *testing.T) {
  32. configJSONTemplate := `
  33. {
  34. "Schemes" : [
  35. {
  36. "Epoch" : "%s",
  37. "Regions" : ["US", "CA"],
  38. "PropagationChannelIDs" : ["2995DB0C968C59C4F23E87988D9C0D41", "E742C25A6D8BA8C17F37E725FA628569", "B4A780E67695595FA486E9B900EA7335"],
  39. "MasterKey" : "wFuSbqU/pJ/35vRmoM8T9ys1PgDa8uzJps1Y+FNKa5U=",
  40. "SeedSpecs" : [
  41. {
  42. "Description": "spec1",
  43. "ID" : "IXHWfVgWFkEKvgqsjmnJuN3FpaGuCzQMETya+DSQvsk=",
  44. "UpstreamSubnets" : ["192.168.0.0/16", "172.16.0.0/12"],
  45. "Targets" :
  46. {
  47. "BytesRead" : 1,
  48. "BytesWritten" : 1,
  49. "PortForwardDurationNanoseconds" : 1
  50. }
  51. },
  52. {
  53. "Description": "spec2",
  54. "ID" : "qvpIcORLE2Pi5TZmqRtVkEp+OKov0MhfsYPLNV7FYtI=",
  55. "UpstreamSubnets" : ["192.168.0.0/16", "10.0.0.0/8"],
  56. "Targets" :
  57. {
  58. "BytesRead" : 10,
  59. "BytesWritten" : 10,
  60. "PortForwardDurationNanoseconds" : 10
  61. }
  62. },
  63. {
  64. "Description": "spec3",
  65. "ID" : "ts5LInjFHbVKX+/C5/bSJqUh+cLT5kJy92TZGLvAtPU=",
  66. "UpstreamSubnets" : ["100.64.0.0/10"],
  67. "Targets" :
  68. {
  69. "BytesRead" : 100,
  70. "BytesWritten" : 100,
  71. "PortForwardDurationNanoseconds" : 100
  72. }
  73. }
  74. ],
  75. "SeedSpecThreshold" : 2,
  76. "SeedPeriodNanoseconds" : 5000000,
  77. "SeedPeriodKeySplits": [
  78. {
  79. "Total": 10,
  80. "Threshold": 5
  81. },
  82. {
  83. "Total": 10,
  84. "Threshold": 5
  85. }
  86. ]
  87. },
  88. {
  89. "Epoch" : "%s",
  90. "Regions" : ["US", "CA"],
  91. "PropagationChannelIDs" : ["36F1CF2DF1250BF0C7BA0629CE3DC657", "B4A780E67695595FA486E9B900EA7335"],
  92. "MasterKey" : "fcyQy8JSxLXHt/Iom9Qj9wMnSjrsccTiiSPEsJicet4=",
  93. "SeedSpecs" : [
  94. {
  95. "Description": "spec1",
  96. "ID" : "NXY0/4lqMxx5XIszIhMbwHobH/qb2Gl0Bw/OGndc1vM=",
  97. "UpstreamSubnets" : ["192.168.0.0/16", "172.16.0.0/12"],
  98. "Targets" :
  99. {
  100. "BytesRead" : 1,
  101. "BytesWritten" : 1,
  102. "PortForwardDurationNanoseconds" : 1
  103. }
  104. },
  105. {
  106. "Description": "spec2",
  107. "ID" : "o78G6muv3idtbQKXoU05tF6gTlQj1LHmNe0eUWkZGxs=",
  108. "UpstreamSubnets" : ["192.168.0.0/16", "10.0.0.0/8"],
  109. "Targets" :
  110. {
  111. "BytesRead" : 10,
  112. "BytesWritten" : 10,
  113. "PortForwardDurationNanoseconds" : 10
  114. }
  115. },
  116. {
  117. "Description": "spec3",
  118. "ID" : "1DlAvJYpoSEfcqMXYBV7bDEtYu3LCQO39ISD5tmi8Uo=",
  119. "UpstreamSubnets" : ["100.64.0.0/10"],
  120. "Targets" :
  121. {
  122. "BytesRead" : 0,
  123. "BytesWritten" : 0,
  124. "PortForwardDurationNanoseconds" : 0
  125. }
  126. }
  127. ],
  128. "SeedSpecThreshold" : 2,
  129. "SeedPeriodNanoseconds" : 5000000,
  130. "SeedPeriodKeySplits": [
  131. {
  132. "Total": 100,
  133. "Threshold": 25
  134. }
  135. ]
  136. }
  137. ]
  138. }
  139. `
  140. seedPeriod := 5 * time.Millisecond // "SeedPeriodNanoseconds" : 5000000
  141. now := time.Now().UTC()
  142. epoch := now.Add(-seedPeriod).Truncate(seedPeriod)
  143. epochStr := epoch.Format(time.RFC3339Nano)
  144. configJSON := fmt.Sprintf(configJSONTemplate, epochStr, epochStr)
  145. // The first scheme requires sufficient activity within 5/10 5 millisecond
  146. // periods and 5/10 50 millisecond longer periods. The second scheme requires
  147. // sufficient activity within 25/100 5 millisecond periods.
  148. config, err := LoadConfig([]byte(configJSON))
  149. if err != nil {
  150. t.Fatalf("LoadConfig failed: %s", err)
  151. }
  152. t.Run("ineligible client, sufficient transfer", func(t *testing.T) {
  153. clientSeedState := config.NewClientSeedState("US", "C5E8D2EDFD093B50D8D65CF59D0263CA", nil)
  154. seedPortForward := clientSeedState.NewClientSeedPortForward(net.ParseIP("192.168.0.1"))
  155. if seedPortForward != nil {
  156. t.Fatalf("expected nil client seed port forward")
  157. }
  158. })
  159. // This clientSeedState is used across multiple tests.
  160. signalIssueSLOKs := make(chan struct{}, 1)
  161. clientSeedState := config.NewClientSeedState("US", "2995DB0C968C59C4F23E87988D9C0D41", signalIssueSLOKs)
  162. t.Run("eligible client, no transfer", func(t *testing.T) {
  163. if len(clientSeedState.GetSeedPayload().SLOKs) != 0 {
  164. t.Fatalf("expected 0 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  165. }
  166. })
  167. t.Run("eligible client, insufficient transfer", func(t *testing.T) {
  168. clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1")).UpdateProgress(5, 5, 5)
  169. if len(clientSeedState.GetSeedPayload().SLOKs) != 0 {
  170. t.Fatalf("expected 0 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  171. }
  172. })
  173. rolloverToNextSLOKTime := func() {
  174. // Rollover to the next SLOK time, so accrued data transfer will be reset.
  175. now := time.Now().UTC()
  176. time.Sleep(now.Add(seedPeriod).Truncate(seedPeriod).Sub(now))
  177. }
  178. t.Run("eligible client, insufficient transfer after rollover", func(t *testing.T) {
  179. rolloverToNextSLOKTime()
  180. clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1")).UpdateProgress(5, 5, 5)
  181. if len(clientSeedState.GetSeedPayload().SLOKs) != 0 {
  182. t.Fatalf("expected 0 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  183. }
  184. })
  185. t.Run("eligible client, sufficient transfer, one port forward", func(t *testing.T) {
  186. rolloverToNextSLOKTime()
  187. clientSeedPortForward := clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1"))
  188. clientSeedPortForward.UpdateProgress(5, 5, 5)
  189. clientSeedPortForward.UpdateProgress(5, 5, 5)
  190. select {
  191. case <-signalIssueSLOKs:
  192. default:
  193. t.Fatalf("expected issue SLOKs signal")
  194. }
  195. if len(clientSeedState.GetSeedPayload().SLOKs) != 1 {
  196. t.Fatalf("expected 1 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  197. }
  198. })
  199. t.Run("eligible client, sufficient transfer, multiple port forwards", func(t *testing.T) {
  200. rolloverToNextSLOKTime()
  201. clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1")).UpdateProgress(5, 5, 5)
  202. clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1")).UpdateProgress(5, 5, 5)
  203. select {
  204. case <-signalIssueSLOKs:
  205. default:
  206. t.Fatalf("expected issue SLOKs signal")
  207. }
  208. // Expect 2 SLOKS: 1 new, and 1 remaining in payload.
  209. if len(clientSeedState.GetSeedPayload().SLOKs) != 2 {
  210. t.Fatalf("expected 2 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  211. }
  212. })
  213. t.Run("eligible client, sufficient transfer multiple SLOKs", func(t *testing.T) {
  214. rolloverToNextSLOKTime()
  215. clientSeedState.NewClientSeedPortForward(net.ParseIP("192.168.0.1")).UpdateProgress(5, 5, 5)
  216. clientSeedState.NewClientSeedPortForward(net.ParseIP("10.0.0.1")).UpdateProgress(5, 5, 5)
  217. select {
  218. case <-signalIssueSLOKs:
  219. default:
  220. t.Fatalf("expected issue SLOKs signal")
  221. }
  222. // Expect 4 SLOKS: 2 new, and 2 remaining in payload.
  223. if len(clientSeedState.GetSeedPayload().SLOKs) != 4 {
  224. t.Fatalf("expected 4 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  225. }
  226. })
  227. t.Run("clear payload", func(t *testing.T) {
  228. clientSeedState.ClearSeedPayload()
  229. if len(clientSeedState.GetSeedPayload().SLOKs) != 0 {
  230. t.Fatalf("expected 0 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  231. }
  232. })
  233. t.Run("no transfer required", func(t *testing.T) {
  234. rolloverToNextSLOKTime()
  235. clientSeedState := config.NewClientSeedState("US", "36F1CF2DF1250BF0C7BA0629CE3DC657", nil)
  236. if len(clientSeedState.GetSeedPayload().SLOKs) != 1 {
  237. t.Fatalf("expected 1 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  238. }
  239. })
  240. t.Run("concurrent schemes", func(t *testing.T) {
  241. rolloverToNextSLOKTime()
  242. clientSeedState := config.NewClientSeedState("US", "B4A780E67695595FA486E9B900EA7335", nil)
  243. clientSeedPortForward := clientSeedState.NewClientSeedPortForward(net.ParseIP("192.168.0.1"))
  244. clientSeedPortForward.UpdateProgress(10, 10, 10)
  245. if len(clientSeedState.GetSeedPayload().SLOKs) != 5 {
  246. t.Fatalf("expected 5 SLOKs, got %d", len(clientSeedState.GetSeedPayload().SLOKs))
  247. }
  248. })
  249. signingPublicKey, signingPrivateKey, err := common.GenerateAuthenticatedDataPackageKeys()
  250. if err != nil {
  251. t.Fatalf("GenerateAuthenticatedDataPackageKeys failed: %s", err)
  252. }
  253. pavedRegistries := make(map[string][]byte)
  254. pavedOSLFileContents := make(map[string]map[string][]byte)
  255. t.Run("pave OSLs", func(t *testing.T) {
  256. // Pave sufficient OSLs to cover simulated elapsed time of all test cases.
  257. endTime := epoch.Add(1000 * seedPeriod)
  258. // In actual deployment, paved files for each propagation channel ID
  259. // are dropped in distinct distribution sites.
  260. for _, propagationChannelID := range []string{
  261. "2995DB0C968C59C4F23E87988D9C0D41",
  262. "E742C25A6D8BA8C17F37E725FA628569",
  263. "36F1CF2DF1250BF0C7BA0629CE3DC657"} {
  264. // Dummy server entry payloads will be the OSL ID, which the following
  265. // tests use to verify that the correct OSL file decrypts successfully.
  266. paveServerEntries := make(map[string][]string)
  267. for _, scheme := range config.Schemes {
  268. oslDuration := scheme.GetOSLDuration()
  269. oslTime := scheme.epoch
  270. for oslTime.Before(endTime) {
  271. firstSLOKRef := &slokReference{
  272. PropagationChannelID: propagationChannelID,
  273. SeedSpecID: string(scheme.SeedSpecs[0].ID),
  274. Time: oslTime,
  275. }
  276. firstSLOK := scheme.deriveSLOK(firstSLOKRef)
  277. oslID := firstSLOK.ID
  278. paveServerEntries[hex.EncodeToString(oslID)] =
  279. []string{base64.StdEncoding.EncodeToString(oslID)}
  280. oslTime = oslTime.Add(oslDuration)
  281. }
  282. }
  283. // Note: these options are exercised in remoteServerList_test.go
  284. omitMD5SumsSchemes := []int{}
  285. omitEmptyOSLsSchemes := []int{}
  286. firstPaveFiles, err := config.Pave(
  287. endTime,
  288. propagationChannelID,
  289. signingPublicKey,
  290. signingPrivateKey,
  291. paveServerEntries,
  292. omitMD5SumsSchemes,
  293. omitEmptyOSLsSchemes,
  294. nil)
  295. if err != nil {
  296. t.Fatalf("Pave failed: %s", err)
  297. }
  298. paveFiles, err := config.Pave(
  299. endTime,
  300. propagationChannelID,
  301. signingPublicKey,
  302. signingPrivateKey,
  303. paveServerEntries,
  304. omitMD5SumsSchemes,
  305. omitEmptyOSLsSchemes,
  306. nil)
  307. if err != nil {
  308. t.Fatalf("Pave failed: %s", err)
  309. }
  310. // Check that the paved file name matches the name the client will look for.
  311. if len(paveFiles) < 1 || paveFiles[len(paveFiles)-1].Name != GetOSLRegistryURL("") {
  312. t.Fatalf("invalid registry pave file")
  313. }
  314. // Check that the content of two paves is the same: all the crypto should be
  315. // deterministic.
  316. for index, paveFile := range paveFiles {
  317. if paveFile.Name != firstPaveFiles[index].Name {
  318. t.Fatalf("Pave name mismatch")
  319. }
  320. if bytes.Compare(paveFile.Contents, firstPaveFiles[index].Contents) != 0 {
  321. t.Fatalf("Pave content mismatch")
  322. }
  323. }
  324. // Use the paved content in the following tests.
  325. pavedRegistries[propagationChannelID] = paveFiles[len(paveFiles)-1].Contents
  326. pavedOSLFileContents[propagationChannelID] = make(map[string][]byte)
  327. for _, paveFile := range paveFiles[0:] {
  328. pavedOSLFileContents[propagationChannelID][paveFile.Name] = paveFile.Contents
  329. }
  330. }
  331. })
  332. if len(pavedRegistries) != 3 {
  333. // Previous subtest failed. Following tests cannot be completed, so abort.
  334. t.Fatalf("pave failed")
  335. }
  336. // To ensure SLOKs are issued at precise time periods, the following tests
  337. // bypass ClientSeedState and derive SLOKs directly.
  338. expandRanges := func(ranges ...[2]int) []int {
  339. a := make([]int, 0)
  340. for _, r := range ranges {
  341. for n := r[0]; n <= r[1]; n++ {
  342. a = append(a, n)
  343. }
  344. }
  345. return a
  346. }
  347. singleSplitPropagationChannelID := "36F1CF2DF1250BF0C7BA0629CE3DC657"
  348. singleSplitScheme := config.Schemes[1]
  349. doubleSplitPropagationChannelID := "2995DB0C968C59C4F23E87988D9C0D41"
  350. doubleSplitScheme := config.Schemes[0]
  351. keySplitTestCases := []struct {
  352. description string
  353. propagationChannelID string
  354. scheme *Scheme
  355. issueSLOKTimePeriods []int
  356. issueSLOKSeedSpecIndexes []int
  357. expectedOSLCount int
  358. }{
  359. {
  360. "single split scheme: insufficient SLOK periods",
  361. singleSplitPropagationChannelID,
  362. singleSplitScheme,
  363. expandRanges([2]int{0, 23}),
  364. []int{0, 1},
  365. 0,
  366. },
  367. {
  368. "single split scheme: insufficient SLOK seed specs",
  369. singleSplitPropagationChannelID,
  370. singleSplitScheme,
  371. expandRanges([2]int{0, 23}),
  372. []int{0},
  373. 0,
  374. },
  375. {
  376. "single split scheme: sufficient SLOKs",
  377. singleSplitPropagationChannelID,
  378. singleSplitScheme,
  379. expandRanges([2]int{0, 24}),
  380. []int{0, 1},
  381. 1,
  382. },
  383. {
  384. "single split scheme: sufficient SLOKs (alternative seed specs)",
  385. singleSplitPropagationChannelID,
  386. singleSplitScheme,
  387. expandRanges([2]int{0, 24}),
  388. []int{1, 2},
  389. 1,
  390. },
  391. {
  392. "single split scheme: more than sufficient SLOKs",
  393. singleSplitPropagationChannelID,
  394. singleSplitScheme,
  395. expandRanges([2]int{0, 49}),
  396. []int{0, 1},
  397. 1,
  398. },
  399. {
  400. "double split scheme: insufficient SLOK periods",
  401. doubleSplitPropagationChannelID,
  402. doubleSplitScheme,
  403. expandRanges([2]int{0, 4}, [2]int{10, 14}, [2]int{20, 24}, [2]int{30, 34}, [2]int{40, 43}),
  404. []int{0, 1},
  405. 0,
  406. },
  407. {
  408. "double split scheme: insufficient SLOK period spread",
  409. doubleSplitPropagationChannelID,
  410. doubleSplitScheme,
  411. expandRanges([2]int{0, 25}),
  412. []int{0, 1},
  413. 0,
  414. },
  415. {
  416. "double split scheme: insufficient SLOK seed specs",
  417. doubleSplitPropagationChannelID,
  418. doubleSplitScheme,
  419. expandRanges([2]int{0, 4}, [2]int{10, 14}, [2]int{20, 24}, [2]int{30, 34}, [2]int{40, 44}),
  420. []int{0},
  421. 0,
  422. },
  423. {
  424. "double split scheme: sufficient SLOKs",
  425. doubleSplitPropagationChannelID,
  426. doubleSplitScheme,
  427. expandRanges([2]int{0, 4}, [2]int{10, 14}, [2]int{20, 24}, [2]int{30, 34}, [2]int{40, 44}),
  428. []int{0, 1},
  429. 1,
  430. },
  431. {
  432. "double split scheme: sufficient SLOKs (alternative seed specs)",
  433. doubleSplitPropagationChannelID,
  434. doubleSplitScheme,
  435. expandRanges([2]int{0, 4}, [2]int{10, 14}, [2]int{20, 24}, [2]int{30, 34}, [2]int{40, 44}),
  436. []int{1, 2},
  437. 1,
  438. },
  439. }
  440. for _, testCase := range keySplitTestCases {
  441. t.Run(testCase.description, func(t *testing.T) {
  442. slokMap := make(map[string][]byte)
  443. for _, timePeriod := range testCase.issueSLOKTimePeriods {
  444. for _, seedSpecIndex := range testCase.issueSLOKSeedSpecIndexes {
  445. slok := testCase.scheme.deriveSLOK(
  446. &slokReference{
  447. PropagationChannelID: testCase.propagationChannelID,
  448. SeedSpecID: string(testCase.scheme.SeedSpecs[seedSpecIndex].ID),
  449. Time: epoch.Add(time.Duration(timePeriod) * seedPeriod),
  450. })
  451. slokMap[string(slok.ID)] = slok.Key
  452. }
  453. }
  454. startTime := time.Now()
  455. lookupSLOKs := func(slokID []byte) []byte {
  456. return slokMap[string(slokID)]
  457. }
  458. registryStreamer, err := NewRegistryStreamer(
  459. bytes.NewReader(pavedRegistries[testCase.propagationChannelID]),
  460. signingPublicKey,
  461. lookupSLOKs)
  462. if err != nil {
  463. t.Fatalf("NewRegistryStreamer failed: %s", err)
  464. }
  465. seededOSLCount := 0
  466. for {
  467. fileSpec, err := registryStreamer.Next()
  468. if err != nil {
  469. t.Fatalf("Next failed: %s", err)
  470. }
  471. if fileSpec == nil {
  472. break
  473. }
  474. seededOSLCount += 1
  475. oslFileContents, ok :=
  476. pavedOSLFileContents[testCase.propagationChannelID][GetOSLFileURL("", fileSpec.ID)]
  477. if !ok {
  478. t.Fatalf("unknown OSL file name")
  479. }
  480. payloadReader, err := NewOSLReader(
  481. bytes.NewReader(oslFileContents),
  482. fileSpec,
  483. lookupSLOKs,
  484. signingPublicKey)
  485. if err != nil {
  486. t.Fatalf("NewOSLReader failed: %s", err)
  487. }
  488. payload, err := ioutil.ReadAll(payloadReader)
  489. if err != nil {
  490. t.Fatalf("ReadAll failed: %s", err)
  491. }
  492. // The decrypted OSL should contain its own ID.
  493. if string(payload) != base64.StdEncoding.EncodeToString(fileSpec.ID) {
  494. t.Fatalf("unexpected OSL file contents")
  495. }
  496. }
  497. t.Logf("registry size: %d", len(pavedRegistries[testCase.propagationChannelID]))
  498. t.Logf("SLOK count: %d", len(slokMap))
  499. t.Logf("seeded OSL count: %d", seededOSLCount)
  500. t.Logf("elapsed time: %s", time.Since(startTime))
  501. if seededOSLCount != testCase.expectedOSLCount {
  502. t.Fatalf("expected %d OSLs got %d", testCase.expectedOSLCount, seededOSLCount)
  503. }
  504. })
  505. }
  506. }