security_provider_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. package ims
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/base64"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "testing"
  13. "time"
  14. "vocat/internal/vowifi"
  15. )
  16. type fakeIPSecInstaller struct {
  17. mu sync.Mutex
  18. configs []IPSecSAConfig
  19. handle *fakeIPSecHandle
  20. }
  21. type fakeIPSecHandle struct {
  22. mu sync.Mutex
  23. closeCount int
  24. }
  25. func (installer *fakeIPSecInstaller) Install(
  26. _ context.Context,
  27. config IPSecSAConfig,
  28. ) (IPSecSAHandle, error) {
  29. if err := validateIPSecSAConfig(config); err != nil {
  30. return nil, err
  31. }
  32. installer.mu.Lock()
  33. defer installer.mu.Unlock()
  34. installer.configs = append(installer.configs, cloneIPSecSAConfig(config))
  35. if installer.handle == nil {
  36. installer.handle = &fakeIPSecHandle{}
  37. }
  38. return installer.handle, nil
  39. }
  40. func (installer *fakeIPSecInstaller) installed() []IPSecSAConfig {
  41. installer.mu.Lock()
  42. defer installer.mu.Unlock()
  43. result := make([]IPSecSAConfig, 0, len(installer.configs))
  44. for _, config := range installer.configs {
  45. result = append(result, cloneIPSecSAConfig(config))
  46. }
  47. return result
  48. }
  49. func (handle *fakeIPSecHandle) Close(context.Context) error {
  50. handle.mu.Lock()
  51. defer handle.mu.Unlock()
  52. handle.closeCount++
  53. return nil
  54. }
  55. func (handle *fakeIPSecHandle) closes() int {
  56. handle.mu.Lock()
  57. defer handle.mu.Unlock()
  58. return handle.closeCount
  59. }
  60. func TestProviderNegotiatesIPSecAndRegistersOverProtectedTCP(t *testing.T) {
  61. localIP := net.ParseIP("127.0.0.1")
  62. remoteIP := net.ParseIP("127.0.0.2")
  63. initial, err := net.ListenTCP("tcp", &net.TCPAddr{IP: remoteIP})
  64. if err != nil {
  65. t.Skipf("secondary loopback address is unavailable: %v", err)
  66. }
  67. defer initial.Close()
  68. protected, err := net.ListenTCP("tcp", &net.TCPAddr{IP: remoteIP})
  69. if err != nil {
  70. t.Fatalf("ListenTCP(protected) error = %v", err)
  71. }
  72. defer protected.Close()
  73. for _, listener := range []*net.TCPListener{initial, protected} {
  74. if err := listener.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
  75. t.Fatalf("SetDeadline() error = %v", err)
  76. }
  77. }
  78. ueClientPort, err := availableProtectedPort(localIP, 0)
  79. if err != nil {
  80. t.Fatalf("availableProtectedPort(client) error = %v", err)
  81. }
  82. ueServerPort, err := availableProtectedPort(localIP, ueClientPort)
  83. if err != nil {
  84. t.Fatalf("availableProtectedPort(server) error = %v", err)
  85. }
  86. pcscfClientPort, err := availableProtectedPort(remoteIP, protected.Addr().(*net.TCPAddr).Port)
  87. if err != nil {
  88. t.Fatalf("availableProtectedPort(P-CSCF client) error = %v", err)
  89. }
  90. nonceBytes := make([]byte, 32)
  91. for index := range nonceBytes {
  92. nonceBytes[index] = byte(index + 1)
  93. }
  94. nonce := base64.StdEncoding.EncodeToString(nonceBytes)
  95. serverEvidence := make(chan protectedRegistrarEvidence, 1)
  96. serverDone := make(chan error, 1)
  97. go func() {
  98. evidence, err := serveProtectedRegistrar(
  99. initial,
  100. protected,
  101. pcscfClientPort,
  102. nonce,
  103. ueClientPort,
  104. ueServerPort,
  105. )
  106. if err == nil {
  107. serverEvidence <- evidence
  108. }
  109. serverDone <- err
  110. }()
  111. installer := &fakeIPSecInstaller{}
  112. aka := &recordingAKA{
  113. result: vowifi.AKAResult{
  114. RES: []byte{1, 2, 3, 4, 5, 6, 7, 8},
  115. CK: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
  116. IK: []byte{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
  117. },
  118. }
  119. initialAddress := initial.Addr().String()
  120. provider, err := NewProvider(aka, Config{
  121. PCSCF: initialAddress,
  122. LocalAddress: localIP.String(),
  123. Transport: "tcp",
  124. TransactionTimeout: 3 * time.Second,
  125. SecurityMode: SecurityRequired,
  126. IPSecInstaller: installer,
  127. ProtectedClientPort: ueClientPort,
  128. ProtectedServerPort: ueServerPort,
  129. })
  130. if err != nil {
  131. t.Fatalf("NewProvider() error = %v", err)
  132. }
  133. session, err := provider.Start(context.Background(), vowifi.IMSRequest{
  134. DeviceID: "modem0",
  135. Identity: vowifi.SIMIdentity{
  136. ICCID: "8901000000000000000",
  137. IMSI: "001010123456789",
  138. HomeMCC: "001",
  139. HomeMNC: "01",
  140. },
  141. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  142. Established: true,
  143. LocalIPv4: localIP.String(),
  144. PCSCF: []string{initialAddress},
  145. }},
  146. })
  147. if err != nil {
  148. t.Fatalf("Provider.Start() error = %v", err)
  149. }
  150. evidence := session.Evidence()
  151. if !evidence.Registered ||
  152. evidence.RegistrationState != "registered" ||
  153. evidence.SecurityMode != "ipsec-3gpp" ||
  154. !evidence.SecurityVerified {
  155. t.Fatalf("registration evidence = %#v", evidence)
  156. }
  157. number, source, ok := vowifi.ExtractAssociatedMSISDN(evidence)
  158. if !ok || number != "+8613800138000" || source != vowifi.PhoneSourcePAssociatedURI {
  159. t.Fatalf("ExtractAssociatedMSISDN() = (%q, %q, %t)", number, source, ok)
  160. }
  161. if sms, err := session.EnableSMS(context.Background()); err != nil || !sms.Ready {
  162. t.Fatalf("EnableSMS() = (%#v, %v)", sms, err)
  163. }
  164. configs := installer.installed()
  165. if len(configs) != 1 {
  166. t.Fatalf("IPsec install count = %d, want 1", len(configs))
  167. }
  168. config := configs[0]
  169. if !config.LocalIP.Equal(localIP) ||
  170. !config.RemoteIP.Equal(remoteIP) ||
  171. config.UEClientPort != ueClientPort ||
  172. config.UEServerPort != ueServerPort ||
  173. config.PCSCFClientPort != pcscfClientPort ||
  174. config.PCSCFServerPort != protected.Addr().(*net.TCPAddr).Port {
  175. t.Fatalf("IPsec config endpoints = %#v", config)
  176. }
  177. if got, want := config.EncryptionKey, aka.result.CK; string(got) != string(want) {
  178. t.Fatalf("encryption key = %v, want CK %v", got, want)
  179. }
  180. wantIntegrity := append(append([]byte(nil), aka.result.IK...), 0, 0, 0, 0)
  181. if string(config.IntegrityKey) != string(wantIntegrity) {
  182. t.Fatalf("integrity key = %v, want %v", config.IntegrityKey, wantIntegrity)
  183. }
  184. if err := session.Close(context.Background()); err != nil {
  185. t.Fatalf("Close() error = %v", err)
  186. }
  187. if err := <-serverDone; err != nil {
  188. t.Fatalf("protected registrar error = %v", err)
  189. }
  190. registrar := <-serverEvidence
  191. if registrar.securityClient == "" ||
  192. registrar.securityVerify != registrar.securityServer {
  193. t.Fatalf("security agreement evidence = %#v", registrar)
  194. }
  195. if installer.handle == nil || installer.handle.closes() != 1 {
  196. t.Fatalf("IPsec handle close count = %v", installer.handle)
  197. }
  198. }
  199. func TestProviderRequiresSecurityServerBeforeAKA(t *testing.T) {
  200. listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  201. if err != nil {
  202. t.Fatalf("ListenUDP() error = %v", err)
  203. }
  204. defer listener.Close()
  205. if err := listener.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
  206. t.Fatalf("SetDeadline() error = %v", err)
  207. }
  208. nonce := base64.StdEncoding.EncodeToString(make([]byte, 32))
  209. serverDone := make(chan error, 1)
  210. go func() {
  211. packet := make([]byte, 65535)
  212. count, remote, err := listener.ReadFromUDP(packet)
  213. if err != nil {
  214. serverDone <- err
  215. return
  216. }
  217. _, headers, err := parseTestRequest(packet[:count])
  218. if err != nil {
  219. serverDone <- err
  220. return
  221. }
  222. if headers["security-client"] == "" ||
  223. !strings.Contains(headers["authorization"], "integrity-protected=no") {
  224. serverDone <- fmt.Errorf("initial security headers = %#v", headers)
  225. return
  226. }
  227. serverDone <- func() error {
  228. _, err := listener.WriteToUDP(testResponse(
  229. 401,
  230. "Unauthorized",
  231. headers["call-id"],
  232. headers["cseq"],
  233. []string{
  234. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` +
  235. nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  236. },
  237. ), remote)
  238. return err
  239. }()
  240. }()
  241. aka := &recordingAKA{}
  242. address := listener.LocalAddr().String()
  243. provider, err := NewProvider(aka, Config{
  244. PCSCF: address,
  245. LocalAddress: "127.0.0.1",
  246. Transport: "udp",
  247. TransactionTimeout: 2 * time.Second,
  248. SecurityMode: SecurityRequired,
  249. IPSecInstaller: &fakeIPSecInstaller{},
  250. })
  251. if err != nil {
  252. t.Fatalf("NewProvider() error = %v", err)
  253. }
  254. _, err = provider.Start(context.Background(), vowifi.IMSRequest{
  255. Identity: vowifi.SIMIdentity{
  256. IMSI: "001010123456789", HomeMCC: "001", HomeMNC: "01",
  257. },
  258. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  259. Established: true,
  260. LocalIPv4: "127.0.0.1",
  261. PCSCF: []string{address},
  262. }},
  263. })
  264. if !errors.Is(err, ErrIPSecAgreementRequired) {
  265. t.Fatalf("Provider.Start() error = %v, want ErrIPSecAgreementRequired", err)
  266. }
  267. if len(aka.challenges) != 0 {
  268. t.Fatalf("AKA challenge count = %d, want 0 before a valid security offer", len(aka.challenges))
  269. }
  270. if err := <-serverDone; err != nil {
  271. t.Fatalf("registrar error = %v", err)
  272. }
  273. }
  274. func TestProviderRejectsIMSAddressOverridesOutsideTunnelEvidence(t *testing.T) {
  275. identity := vowifi.SIMIdentity{
  276. IMSI: "001010123456789", HomeMCC: "001", HomeMNC: "01",
  277. }
  278. for _, test := range []struct {
  279. name string
  280. config Config
  281. tunnel vowifi.TunnelEvidence
  282. errorMatch string
  283. }{
  284. {
  285. name: "P-CSCF",
  286. config: Config{
  287. PCSCF: "127.0.0.1:25000",
  288. LocalAddress: "127.0.0.3",
  289. Transport: "tcp",
  290. SecurityMode: SecurityDisabled,
  291. },
  292. tunnel: vowifi.TunnelEvidence{
  293. Established: true,
  294. LocalIPv4: "127.0.0.3",
  295. PCSCF: []string{"127.0.0.2:25000"},
  296. },
  297. errorMatch: "P-CSCF",
  298. },
  299. {
  300. name: "local address",
  301. config: Config{
  302. PCSCF: "127.0.0.2:25000",
  303. LocalAddress: "127.0.0.3",
  304. Transport: "tcp",
  305. SecurityMode: SecurityDisabled,
  306. },
  307. tunnel: vowifi.TunnelEvidence{
  308. Established: true,
  309. LocalIPv4: "127.0.0.4",
  310. PCSCF: []string{"127.0.0.2:25000"},
  311. },
  312. errorMatch: "local address",
  313. },
  314. } {
  315. t.Run(test.name, func(t *testing.T) {
  316. provider, err := NewProvider(&recordingAKA{}, test.config)
  317. if err != nil {
  318. t.Fatalf("NewProvider() error = %v", err)
  319. }
  320. _, err = provider.Start(context.Background(), vowifi.IMSRequest{
  321. Identity: identity,
  322. Tunnel: evidenceTunnel{evidence: test.tunnel},
  323. })
  324. if err == nil || !strings.Contains(err.Error(), test.errorMatch) {
  325. t.Fatalf("Provider.Start() error = %v, want %q", err, test.errorMatch)
  326. }
  327. })
  328. }
  329. }
  330. type protectedRegistrarEvidence struct {
  331. securityClient string
  332. securityServer string
  333. securityVerify string
  334. }
  335. func serveProtectedRegistrar(
  336. initialListener *net.TCPListener,
  337. protectedListener *net.TCPListener,
  338. pcscfClientPort int,
  339. nonce string,
  340. wantUEClientPort int,
  341. wantUEServerPort int,
  342. ) (protectedRegistrarEvidence, error) {
  343. var result protectedRegistrarEvidence
  344. initialConnection, err := initialListener.AcceptTCP()
  345. if err != nil {
  346. return result, err
  347. }
  348. _ = initialConnection.SetDeadline(time.Now().Add(5 * time.Second))
  349. initialReader := bufio.NewReader(initialConnection)
  350. packet, err := readTestTCPRequest(initialReader)
  351. if err != nil {
  352. _ = initialConnection.Close()
  353. return result, err
  354. }
  355. startLine, headers, err := parseTestRequest(packet)
  356. if err != nil {
  357. _ = initialConnection.Close()
  358. return result, err
  359. }
  360. if !strings.HasPrefix(startLine, "REGISTER ") {
  361. _ = initialConnection.Close()
  362. return result, fmt.Errorf("initial method = %q, want REGISTER", startLine)
  363. }
  364. if headers["require"] != "sec-agree" || headers["proxy-require"] != "sec-agree" {
  365. _ = initialConnection.Close()
  366. return result, fmt.Errorf("initial sec-agree headers = %#v", headers)
  367. }
  368. result.securityClient = headers["security-client"]
  369. proposal, err := parseSecurityMechanism(result.securityClient)
  370. if err != nil {
  371. _ = initialConnection.Close()
  372. return result, fmt.Errorf("parse initial Security-Client: %w", err)
  373. }
  374. if proposal.portClient != wantUEClientPort || proposal.portServer != wantUEServerPort {
  375. _ = initialConnection.Close()
  376. return result, fmt.Errorf("UE protected ports = (%d, %d)", proposal.portClient, proposal.portServer)
  377. }
  378. if !strings.Contains(headers["contact"], net.JoinHostPort("127.0.0.1", strconv.Itoa(wantUEServerPort))) {
  379. _ = initialConnection.Close()
  380. return result, fmt.Errorf("initial Contact = %q", headers["contact"])
  381. }
  382. authDirectives, err := testDigestDirectives(headers["authorization"])
  383. if err != nil {
  384. _ = initialConnection.Close()
  385. return result, err
  386. }
  387. if authDirectives["nonce"] != "" ||
  388. authDirectives["response"] != "" ||
  389. authDirectives["integrity-protected"] != "no" {
  390. _ = initialConnection.Close()
  391. return result, fmt.Errorf("initial Authorization = %#v", authDirectives)
  392. }
  393. pcscfClientSPI, pcscfServerSPI := nonCollidingServerSPIs(
  394. proposal.spiClient,
  395. proposal.spiServer,
  396. )
  397. result.securityServer = fmt.Sprintf(
  398. "ipsec-3gpp;q=0.100;alg=hmac-sha-1-96;prot=esp;mod=trans;"+
  399. "ealg=aes-cbc;spi-c=%d;spi-s=%d;port-c=%d;port-s=%d",
  400. pcscfClientSPI,
  401. pcscfServerSPI,
  402. pcscfClientPort,
  403. protectedListener.Addr().(*net.TCPAddr).Port,
  404. )
  405. callID := headers["call-id"]
  406. if _, err := initialConnection.Write(testResponse(
  407. 401,
  408. "Unauthorized",
  409. callID,
  410. headers["cseq"],
  411. []string{
  412. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` +
  413. nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  414. "Security-Server: " + result.securityServer,
  415. },
  416. )); err != nil {
  417. _ = initialConnection.Close()
  418. return result, err
  419. }
  420. _ = initialConnection.Close()
  421. protectedConnection, err := protectedListener.AcceptTCP()
  422. if err != nil {
  423. return result, err
  424. }
  425. defer protectedConnection.Close()
  426. if err := protectedConnection.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
  427. return result, err
  428. }
  429. if got := protectedConnection.RemoteAddr().(*net.TCPAddr).Port; got != wantUEClientPort {
  430. return result, fmt.Errorf("protected TCP source port = %d, want %d", got, wantUEClientPort)
  431. }
  432. protectedReader := bufio.NewReader(protectedConnection)
  433. packet, err = readTestTCPRequest(protectedReader)
  434. if err != nil {
  435. return result, err
  436. }
  437. startLine, headers, err = parseTestRequest(packet)
  438. if err != nil {
  439. return result, err
  440. }
  441. if !strings.HasPrefix(startLine, "REGISTER ") {
  442. return result, fmt.Errorf("protected method = %q, want REGISTER", startLine)
  443. }
  444. if headers["security-client"] != result.securityClient {
  445. return result, fmt.Errorf(
  446. "protected Security-Client = %q, want %q",
  447. headers["security-client"],
  448. result.securityClient,
  449. )
  450. }
  451. result.securityVerify = headers["security-verify"]
  452. if result.securityVerify != result.securityServer {
  453. return result, fmt.Errorf(
  454. "Security-Verify = %q, want %q",
  455. result.securityVerify,
  456. result.securityServer,
  457. )
  458. }
  459. if err := verifyTestAuthorization(headers["authorization"], nonce); err != nil {
  460. return result, err
  461. }
  462. protectedAuth, err := testDigestDirectives(headers["authorization"])
  463. if err != nil {
  464. return result, err
  465. }
  466. if protectedAuth["integrity-protected"] != "yes" {
  467. return result, fmt.Errorf("protected Authorization = %#v", protectedAuth)
  468. }
  469. if !strings.Contains(headers["contact"], net.JoinHostPort("127.0.0.1", strconv.Itoa(wantUEServerPort))) {
  470. return result, fmt.Errorf("protected Contact = %q", headers["contact"])
  471. }
  472. if strings.Contains(strings.ToUpper(startLine), "MESSAGE") ||
  473. strings.Contains(strings.ToUpper(headers["allow"]), "MESSAGE") {
  474. return result, errors.New("registration transaction advertised or sent MESSAGE")
  475. }
  476. if _, err := protectedConnection.Write(testResponse(
  477. 200,
  478. "OK",
  479. callID,
  480. headers["cseq"],
  481. []string{
  482. "P-Associated-URI: <sip:[email protected]>, <tel:+8613800138000>",
  483. "Contact: " + headers["contact"] + ";expires=600",
  484. "Service-Route: <sip:route.ims.example;lr>",
  485. },
  486. )); err != nil {
  487. return result, err
  488. }
  489. packet, err = readTestTCPRequest(protectedReader)
  490. if err != nil {
  491. return result, err
  492. }
  493. startLine, headers, err = parseTestRequest(packet)
  494. if err != nil {
  495. return result, err
  496. }
  497. if !strings.HasPrefix(startLine, "REGISTER ") || headers["expires"] != "0" {
  498. return result, fmt.Errorf("deregistration request = %q, headers %#v", startLine, headers)
  499. }
  500. if headers["security-verify"] != result.securityServer {
  501. return result, fmt.Errorf("deregistration Security-Verify = %q", headers["security-verify"])
  502. }
  503. if _, err := protectedConnection.Write(
  504. testResponse(200, "OK", callID, headers["cseq"], nil),
  505. ); err != nil {
  506. return result, err
  507. }
  508. return result, nil
  509. }
  510. func readTestTCPRequest(reader *bufio.Reader) ([]byte, error) {
  511. var request strings.Builder
  512. for {
  513. line, err := reader.ReadString('\n')
  514. if err != nil {
  515. return nil, err
  516. }
  517. request.WriteString(line)
  518. if line == "\r\n" || line == "\n" {
  519. return []byte(request.String()), nil
  520. }
  521. if request.Len() > 64*1024 {
  522. return nil, errors.New("SIP request headers are too large")
  523. }
  524. }
  525. }
  526. func testDigestDirectives(value string) (map[string]string, error) {
  527. scheme, parameters, found := strings.Cut(value, " ")
  528. if !found || !strings.EqualFold(scheme, "Digest") {
  529. return nil, errors.New("Authorization is not Digest")
  530. }
  531. return parseAuthDirectives(parameters)
  532. }
  533. func nonCollidingServerSPIs(ueClient uint32, ueServer uint32) (uint32, uint32) {
  534. client := uint32(0x70000001)
  535. for client == ueClient || client == ueServer || client == 0 {
  536. client++
  537. }
  538. server := client + 1
  539. for server == ueClient || server == ueServer || server == client || server == 0 {
  540. server++
  541. }
  542. return client, server
  543. }