esim_disable.go 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package device
  2. import (
  3. "context"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "strings"
  8. )
  9. var (
  10. ErrESIMDisableProfileNotFound = errors.New("esim: profile to disable was not found on the eUICC")
  11. ErrESIMProfileNotEnabled = errors.New("esim: profile is not currently enabled")
  12. ErrESIMDisableDisallowedByPolicy = errors.New("esim: profile disabling is not allowed by its policy")
  13. ErrESIMDisableCATBusy = errors.New("esim: card application toolkit is busy; retry disabling later")
  14. )
  15. func buildDisableProfileRequest(iccid string) ([]byte, error) {
  16. bcd, err := encodeICCID(strings.TrimSpace(iccid))
  17. if err != nil {
  18. return nil, err
  19. }
  20. // SGP.22 ES10c DisableProfileRequest:
  21. // BF32 { A0 { 5A <ICCID BCD> } 81 01 FF } (refreshFlag = true).
  22. profileID := derConstruct(0xA0, derEncode(0x5A, bcd))
  23. return derConstruct(0xBF32, profileID, derEncode(0x81, []byte{0xFF})), nil
  24. }
  25. func disableProfileResult(payload []byte) (byte, bool) {
  26. nodes := derParse(payload)
  27. if len(nodes) != 1 || nodes[0].tag != 0xBF32 {
  28. return 0, false
  29. }
  30. result := derFindValue(payload, 0x80)
  31. if len(result) != 1 {
  32. return 0, false
  33. }
  34. return result[0], true
  35. }
  36. func disableProfileResponseError(result byte, payload []byte) error {
  37. raw := strings.ToUpper(hex.EncodeToString(payload))
  38. switch result {
  39. case 0:
  40. return nil
  41. case 1:
  42. return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableProfileNotFound, result, raw)
  43. case 2:
  44. return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMProfileNotEnabled, result, raw)
  45. case 3:
  46. return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableDisallowedByPolicy, result, raw)
  47. case 5:
  48. return fmt.Errorf("%w (result=0x%02X, raw %s)", ErrESIMDisableCATBusy, result, raw)
  49. default:
  50. return fmt.Errorf("esim: eUICC rejected DisableProfile, result=0x%02X (raw %s)", result, raw)
  51. }
  52. }
  53. // ESIMDisableProfile disables the currently enabled profile through ES10c.
  54. // With refreshFlag=true, the modem must be reset/re-discovered after commit.
  55. func (manager *Manager) ESIMDisableProfile(ctx context.Context, id, iccid, aidHex string) error {
  56. request, err := buildDisableProfileRequest(iccid)
  57. if err != nil {
  58. return err
  59. }
  60. manager.esimMu.Lock()
  61. defer manager.esimMu.Unlock()
  62. if err := manager.waitForESIMRecovery(ctx, id); err != nil {
  63. return err
  64. }
  65. channel, err := manager.openEuiccAID(ctx, id, targetEuiccAID(aidHex))
  66. if err != nil {
  67. return err
  68. }
  69. commitContext, cancelCommit := context.WithTimeout(context.WithoutCancel(ctx), csimAPDUTimeout)
  70. payload, err := channel.es10(commitContext, request)
  71. cancelCommit()
  72. closeContext, cancelClose := context.WithTimeout(context.Background(), csimAPDUTimeout)
  73. channel.close(closeContext)
  74. cancelClose()
  75. if err != nil {
  76. // The card may have committed immediately before a transport failure.
  77. manager.startProfileSwitchRecovery(id)
  78. return err
  79. }
  80. result, ok := disableProfileResult(payload)
  81. if !ok {
  82. manager.startProfileSwitchRecovery(id)
  83. return fmt.Errorf("esim: unexpected DisableProfile response %s", strings.ToUpper(hex.EncodeToString(payload)))
  84. }
  85. if err := disableProfileResponseError(result, payload); err != nil {
  86. return err
  87. }
  88. manager.markCachedProfileDisabled(id, strings.TrimSpace(iccid))
  89. manager.startProfileSwitchRecovery(id)
  90. return nil
  91. }