provider_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. package ims
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "strconv"
  9. "strings"
  10. "testing"
  11. "time"
  12. "vocat/internal/vowifi"
  13. )
  14. type evidenceTunnel struct {
  15. evidence vowifi.TunnelEvidence
  16. }
  17. func (tunnel evidenceTunnel) Evidence() vowifi.TunnelEvidence {
  18. return tunnel.evidence
  19. }
  20. func (evidenceTunnel) Close(context.Context) error {
  21. return nil
  22. }
  23. func TestProviderRegisterAKAParseEvidenceAndClose(t *testing.T) {
  24. for _, test := range []struct {
  25. name string
  26. confirmSMS bool
  27. wantSMSReady bool
  28. }{
  29. {name: "registrar confirms SMS feature tag", confirmSMS: true, wantSMSReady: true},
  30. {name: "registrar omits SMS feature tag", confirmSMS: false, wantSMSReady: false},
  31. } {
  32. t.Run(test.name, func(t *testing.T) {
  33. listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  34. if err != nil {
  35. t.Fatalf("ListenUDP() error = %v", err)
  36. }
  37. defer listener.Close()
  38. if err := listener.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
  39. t.Fatalf("SetDeadline() error = %v", err)
  40. }
  41. nonceBytes := make([]byte, 32)
  42. for index := range nonceBytes {
  43. nonceBytes[index] = byte(index + 1)
  44. }
  45. nonce := base64.StdEncoding.EncodeToString(nonceBytes)
  46. serverDone := make(chan error, 1)
  47. go func() {
  48. serverDone <- serveRegistration(listener, nonce, test.confirmSMS)
  49. }()
  50. aka := &recordingAKA{
  51. result: vowifi.AKAResult{RES: []byte{1, 2, 3, 4, 5, 6, 7, 8}},
  52. }
  53. provider, err := NewProvider(aka, Config{
  54. PCSCF: listener.LocalAddr().String(),
  55. LocalAddress: "127.0.0.1",
  56. Transport: "udp",
  57. TransactionTimeout: 3 * time.Second,
  58. SecurityMode: SecurityDisabled,
  59. })
  60. if err != nil {
  61. t.Fatalf("NewProvider() error = %v", err)
  62. }
  63. session, err := provider.Start(context.Background(), vowifi.IMSRequest{
  64. DeviceID: "ec20",
  65. Identity: vowifi.SIMIdentity{
  66. ICCID: "8901000000000000000",
  67. IMSI: "001010123456789",
  68. HomeMCC: "001",
  69. HomeMNC: "01",
  70. },
  71. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  72. Established: true,
  73. LocalIPv4: "127.0.0.1",
  74. PCSCF: []string{listener.LocalAddr().String()},
  75. }},
  76. })
  77. if err != nil {
  78. t.Fatalf("Provider.Start() error = %v", err)
  79. }
  80. evidence := session.Evidence()
  81. if !evidence.Registered || evidence.LastSIPCode != 200 ||
  82. evidence.RegistrationState != "registered" {
  83. t.Fatalf("evidence = %#v", evidence)
  84. }
  85. if len(evidence.AssociatedIdentities) != 2 ||
  86. len(evidence.PAssociatedURI) != 2 ||
  87. len(evidence.ServiceRoute) != 1 {
  88. t.Fatalf("parsed evidence = %#v", evidence)
  89. }
  90. if evidence.RegisteredContact == "" {
  91. t.Fatalf("registered contact was not correlated: %#v", evidence)
  92. }
  93. concrete, ok := session.(*Session)
  94. if !ok {
  95. t.Fatalf("session type = %T", session)
  96. }
  97. if err := concrete.refreshOnce(context.Background()); err != nil {
  98. t.Fatalf("refreshOnce() error = %v", err)
  99. }
  100. evidence = session.Evidence()
  101. if !evidence.Registered || evidence.RegistrationState != "registered" {
  102. t.Fatalf("evidence after refresh = %#v", evidence)
  103. }
  104. number, source, ok := vowifi.ExtractAssociatedMSISDN(evidence)
  105. if !ok || number != "+8613800138000" || source != vowifi.PhoneSourcePAssociatedURI {
  106. t.Fatalf("ExtractAssociatedMSISDN() = (%q, %q, %t)", number, source, ok)
  107. }
  108. sms, smsErr := session.EnableSMS(context.Background())
  109. if test.wantSMSReady {
  110. if smsErr != nil || !sms.Ready {
  111. t.Fatalf("EnableSMS() = (%#v, %v), want ready", sms, smsErr)
  112. }
  113. } else {
  114. if !errors.Is(smsErr, ErrSMSCapabilityNotConfirmed) || sms.Ready {
  115. t.Fatalf("EnableSMS() = (%#v, %v), want strict not-ready", sms, smsErr)
  116. }
  117. }
  118. if err := session.Close(context.Background()); err != nil {
  119. t.Fatalf("Close() error = %v", err)
  120. }
  121. if session.Evidence().Registered {
  122. t.Fatal("Evidence().Registered = true after Close")
  123. }
  124. if err := <-serverDone; err != nil {
  125. t.Fatalf("registrar error = %v", err)
  126. }
  127. if len(aka.challenges) != 1 {
  128. t.Fatalf("AKA challenge count = %d, want 1", len(aka.challenges))
  129. }
  130. })
  131. }
  132. }
  133. func TestRefreshFailureRevokesRegistrationEvidence(t *testing.T) {
  134. listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  135. if err != nil {
  136. t.Fatalf("ListenUDP() error = %v", err)
  137. }
  138. defer listener.Close()
  139. if err := listener.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
  140. t.Fatalf("SetDeadline() error = %v", err)
  141. }
  142. nonce := base64.StdEncoding.EncodeToString(make([]byte, 32))
  143. serverDone := make(chan error, 1)
  144. go func() {
  145. serverDone <- serveRefreshFailure(listener, nonce)
  146. }()
  147. provider, err := NewProvider(
  148. &recordingAKA{result: vowifi.AKAResult{RES: []byte{1, 2, 3, 4}}},
  149. Config{
  150. PCSCF: listener.LocalAddr().String(),
  151. LocalAddress: "127.0.0.1",
  152. Transport: "udp",
  153. TransactionTimeout: 3 * time.Second,
  154. SecurityMode: SecurityDisabled,
  155. },
  156. )
  157. if err != nil {
  158. t.Fatalf("NewProvider() error = %v", err)
  159. }
  160. session, err := provider.Start(context.Background(), vowifi.IMSRequest{
  161. Identity: vowifi.SIMIdentity{
  162. IMSI: "001010123456789", HomeMCC: "001", HomeMNC: "01",
  163. },
  164. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  165. Established: true,
  166. LocalIPv4: "127.0.0.1",
  167. PCSCF: []string{listener.LocalAddr().String()},
  168. }},
  169. })
  170. if err != nil {
  171. t.Fatalf("Provider.Start() error = %v", err)
  172. }
  173. concrete := session.(*Session)
  174. if err := concrete.refreshOnce(context.Background()); err == nil {
  175. t.Fatal("refreshOnce() error = nil, want SIP rejection")
  176. }
  177. evidence := session.Evidence()
  178. if evidence.Registered || evidence.RegistrationState != "refresh_failed" {
  179. t.Fatalf("evidence after failed refresh = %#v", evidence)
  180. }
  181. if sms, err := session.EnableSMS(context.Background()); sms.Ready || !errors.Is(err, vowifi.ErrIMSNotRegistered) {
  182. t.Fatalf("EnableSMS() = (%#v, %v), want IMS not registered", sms, err)
  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("registrar error = %v", err)
  189. }
  190. }
  191. func serveRegistration(listener *net.UDPConn, nonce string, confirmSMS bool) error {
  192. var callID string
  193. for step := 0; step < 4; step++ {
  194. packet := make([]byte, 65535)
  195. count, remote, err := listener.ReadFromUDP(packet)
  196. if err != nil {
  197. return err
  198. }
  199. startLine, headers, err := parseTestRequest(packet[:count])
  200. if err != nil {
  201. return err
  202. }
  203. if !strings.HasPrefix(startLine, "REGISTER sip:ims.mnc001.mcc001.3gppnetwork.org SIP/2.0") {
  204. return fmt.Errorf("unexpected start line %q", startLine)
  205. }
  206. for _, forbidden := range []string{
  207. "p-access-network-info",
  208. "p-visited-network-id",
  209. "p-preferred-identity",
  210. } {
  211. if headers[forbidden] != "" {
  212. return fmt.Errorf(
  213. "REGISTER unexpectedly included %s: %q",
  214. forbidden,
  215. headers[forbidden],
  216. )
  217. }
  218. }
  219. if step == 0 {
  220. if headers["authorization"] != "" {
  221. return errors.New("initial REGISTER unexpectedly authenticated")
  222. }
  223. callID = headers["call-id"]
  224. response := testResponse(
  225. 401,
  226. "Unauthorized",
  227. callID,
  228. headers["cseq"],
  229. []string{
  230. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` +
  231. nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  232. },
  233. )
  234. if _, err := listener.WriteToUDP(response, remote); err != nil {
  235. return err
  236. }
  237. continue
  238. }
  239. if headers["call-id"] != callID {
  240. return errors.New("Call-ID changed within registration")
  241. }
  242. if step == 1 {
  243. if headers["authorization"] == "" {
  244. return errors.New("authenticated REGISTER omitted Authorization")
  245. }
  246. if err := verifyTestAuthorization(headers["authorization"], nonce); err != nil {
  247. return err
  248. }
  249. contact := headers["contact"]
  250. extraContacts := []string(nil)
  251. if !confirmSMS {
  252. contact = strings.Replace(contact, ";+g.3gpp.smsip", "", 1)
  253. extraContacts = append(
  254. extraContacts,
  255. "Contact: <sip:[email protected]:5099;transport=udp>;+g.3gpp.smsip;expires=600",
  256. )
  257. }
  258. responseHeaders := []string{
  259. "P-Associated-URI: <sip:[email protected]>, <tel:+8613800138000>",
  260. "Contact: " + contact + ";expires=600",
  261. "Service-Route: <sip:route.ims.example;lr>",
  262. }
  263. responseHeaders = append(responseHeaders, extraContacts...)
  264. response := testResponse(
  265. 200,
  266. "OK",
  267. callID,
  268. headers["cseq"],
  269. responseHeaders,
  270. )
  271. if _, err := listener.WriteToUDP(response, remote); err != nil {
  272. return err
  273. }
  274. continue
  275. }
  276. if step == 2 {
  277. if headers["expires"] == "0" {
  278. return errors.New("refresh REGISTER used zero expiry")
  279. }
  280. if headers["authorization"] != "" {
  281. return errors.New("refresh reused the one-time AKAv1 RES")
  282. }
  283. contact := headers["contact"]
  284. extraContacts := []string(nil)
  285. if !confirmSMS {
  286. contact = strings.Replace(contact, ";+g.3gpp.smsip", "", 1)
  287. extraContacts = append(
  288. extraContacts,
  289. "Contact: <sip:[email protected]:5099;transport=udp>;+g.3gpp.smsip;expires=600",
  290. )
  291. }
  292. responseHeaders := []string{
  293. "P-Associated-URI: <sip:[email protected]>, <tel:+8613800138000>",
  294. "Contact: " + contact + ";expires=600",
  295. "Service-Route: <sip:route.ims.example;lr>",
  296. }
  297. responseHeaders = append(responseHeaders, extraContacts...)
  298. response := testResponse(
  299. 200,
  300. "OK",
  301. callID,
  302. headers["cseq"],
  303. responseHeaders,
  304. )
  305. if _, err := listener.WriteToUDP(response, remote); err != nil {
  306. return err
  307. }
  308. continue
  309. }
  310. if headers["expires"] != "0" {
  311. return fmt.Errorf("deregister Expires = %q, want 0", headers["expires"])
  312. }
  313. if _, err := listener.WriteToUDP(
  314. testResponse(200, "OK", callID, headers["cseq"], nil),
  315. remote,
  316. ); err != nil {
  317. return err
  318. }
  319. }
  320. return nil
  321. }
  322. func serveRefreshFailure(listener *net.UDPConn, nonce string) error {
  323. var callID string
  324. for step := 0; step < 3; step++ {
  325. packet := make([]byte, 65535)
  326. count, remote, err := listener.ReadFromUDP(packet)
  327. if err != nil {
  328. return err
  329. }
  330. _, headers, err := parseTestRequest(packet[:count])
  331. if err != nil {
  332. return err
  333. }
  334. if step == 0 {
  335. callID = headers["call-id"]
  336. if _, err := listener.WriteToUDP(
  337. testResponse(
  338. 401,
  339. "Unauthorized",
  340. callID,
  341. headers["cseq"],
  342. []string{
  343. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` +
  344. nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  345. },
  346. ),
  347. remote,
  348. ); err != nil {
  349. return err
  350. }
  351. continue
  352. }
  353. if step == 1 {
  354. if headers["authorization"] == "" {
  355. return errors.New("authenticated REGISTER omitted Authorization")
  356. }
  357. if _, err := listener.WriteToUDP(
  358. testResponse(
  359. 200,
  360. "OK",
  361. callID,
  362. headers["cseq"],
  363. []string{
  364. "P-Associated-URI: <tel:+8613800138000>",
  365. "Contact: " + headers["contact"] + ";expires=600",
  366. },
  367. ),
  368. remote,
  369. ); err != nil {
  370. return err
  371. }
  372. continue
  373. }
  374. if headers["authorization"] != "" {
  375. return errors.New("refresh reused the one-time AKAv1 RES")
  376. }
  377. if _, err := listener.WriteToUDP(
  378. testResponse(503, "Service Unavailable", callID, headers["cseq"], nil),
  379. remote,
  380. ); err != nil {
  381. return err
  382. }
  383. }
  384. return nil
  385. }
  386. func parseTestRequest(packet []byte) (string, map[string]string, error) {
  387. text := strings.ReplaceAll(string(packet), "\r\n", "\n")
  388. lines := strings.Split(text, "\n")
  389. if len(lines) < 2 {
  390. return "", nil, errors.New("short SIP request")
  391. }
  392. headers := make(map[string]string)
  393. for _, line := range lines[1:] {
  394. if line == "" {
  395. break
  396. }
  397. name, value, found := strings.Cut(line, ":")
  398. if !found {
  399. return "", nil, fmt.Errorf("malformed request header %q", line)
  400. }
  401. headers[strings.ToLower(strings.TrimSpace(name))] = strings.TrimSpace(value)
  402. }
  403. return lines[0], headers, nil
  404. }
  405. func verifyTestAuthorization(value string, nonce string) error {
  406. scheme, parameters, found := strings.Cut(value, " ")
  407. if !found || scheme != "Digest" {
  408. return errors.New("invalid Authorization scheme")
  409. }
  410. directives, err := parseAuthDirectives(parameters)
  411. if err != nil {
  412. return err
  413. }
  414. expected := digestResponse(
  415. "[email protected]",
  416. "ims.mnc001.mcc001.3gppnetwork.org",
  417. []byte{1, 2, 3, 4, 5, 6, 7, 8},
  418. "REGISTER",
  419. "sip:ims.mnc001.mcc001.3gppnetwork.org",
  420. nonce,
  421. directives["nc"],
  422. directives["cnonce"],
  423. directives["qop"],
  424. )
  425. if directives["response"] != expected {
  426. return fmt.Errorf("digest response = %q, want %q", directives["response"], expected)
  427. }
  428. if directives["algorithm"] != "AKAv1-MD5" || directives["qop"] != "auth" {
  429. return fmt.Errorf("digest directives = %#v", directives)
  430. }
  431. return nil
  432. }
  433. func TestRegistrationRejectionErrorIncludesSafeDiagnostics(t *testing.T) {
  434. err := registrationRejectionError(&sipResponse{
  435. StatusCode: 403,
  436. Reason: "Forbidden\r\nignored",
  437. Headers: map[string][]string{
  438. "reason": {`SIP;cause=403;text="not provisioned"`},
  439. "warning": {`399 pcscf "subscriber barred"`},
  440. "www-authenticate": {`Digest nonce="must-not-leak"`},
  441. },
  442. }, "authenticated")
  443. if !errors.Is(err, ErrRegistrationRejected) {
  444. t.Fatalf("error does not wrap ErrRegistrationRejected: %v", err)
  445. }
  446. message := err.Error()
  447. for _, expected := range []string{
  448. "authenticated REGISTER was rejected",
  449. "SIP 403 Forbidden ignored",
  450. `Reason: SIP;cause=403;text="not provisioned"`,
  451. `Warning: 399 pcscf "subscriber barred"`,
  452. } {
  453. if !strings.Contains(message, expected) {
  454. t.Fatalf("error %q does not contain %q", message, expected)
  455. }
  456. }
  457. if strings.Contains(message, "must-not-leak") {
  458. t.Fatalf("error leaked an authentication header: %q", message)
  459. }
  460. }
  461. func testResponse(
  462. status int,
  463. reason string,
  464. callID string,
  465. cseq string,
  466. extraHeaders []string,
  467. ) []byte {
  468. lines := []string{
  469. "SIP/2.0 " + strconv.Itoa(status) + " " + reason,
  470. "Call-ID: " + callID,
  471. "CSeq: " + cseq,
  472. }
  473. lines = append(lines, extraHeaders...)
  474. lines = append(lines, "Content-Length: 0", "", "")
  475. return []byte(strings.Join(lines, "\r\n"))
  476. }