esim_rename.go 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package device
  2. import (
  3. "context"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "strings"
  8. "unicode/utf8"
  9. )
  10. var (
  11. ErrESIMNicknameTooLong = errors.New("esim: profile nickname must not exceed 64 characters")
  12. ErrESIMNicknameProfileNotFound = errors.New("esim: profile to rename was not found on the eUICC")
  13. )
  14. func buildSetNicknameRequest(iccid, nickname string) ([]byte, error) {
  15. bcd, err := encodeICCID(strings.TrimSpace(iccid))
  16. if err != nil {
  17. return nil, err
  18. }
  19. if !utf8.ValidString(nickname) {
  20. return nil, errors.New("esim: profile nickname is not valid UTF-8")
  21. }
  22. if utf8.RuneCountInString(nickname) > 64 {
  23. return nil, ErrESIMNicknameTooLong
  24. }
  25. // SGP.22 ES10c SetNicknameRequest:
  26. // BF29 { 5A <ICCID BCD> 90 <UTF-8 nickname> }.
  27. return derConstruct(0xBF29, derEncode(0x5A, bcd), derEncode(0x90, []byte(nickname))), nil
  28. }
  29. func setNicknameResult(payload []byte) (byte, bool) {
  30. nodes := derParse(payload)
  31. if len(nodes) != 1 || nodes[0].tag != 0xBF29 {
  32. return 0, false
  33. }
  34. result := derFindValue(payload, 0x80)
  35. if len(result) != 1 {
  36. return 0, false
  37. }
  38. return result[0], true
  39. }
  40. // ESIMRenameProfile updates the on-card Profile Nickname through ES10c. It
  41. // does not enable, disable, download, or delete a profile.
  42. func (manager *Manager) ESIMRenameProfile(ctx context.Context, id, iccid, nickname, aidHex string) error {
  43. request, err := buildSetNicknameRequest(iccid, nickname)
  44. if err != nil {
  45. return err
  46. }
  47. manager.esimMu.Lock()
  48. defer manager.esimMu.Unlock()
  49. if err := manager.waitForESIMRecovery(ctx, id); err != nil {
  50. return err
  51. }
  52. channel, err := manager.openEuiccAID(ctx, id, targetEuiccAID(aidHex))
  53. if err != nil {
  54. return err
  55. }
  56. defer channel.close(context.Background())
  57. commitContext, cancelCommit := context.WithTimeout(context.WithoutCancel(ctx), csimAPDUTimeout)
  58. payload, err := channel.es10(commitContext, request)
  59. cancelCommit()
  60. if err != nil {
  61. return err
  62. }
  63. result, ok := setNicknameResult(payload)
  64. if !ok {
  65. return fmt.Errorf("esim: unexpected SetNickname response %s", strings.ToUpper(hex.EncodeToString(payload)))
  66. }
  67. switch result {
  68. case 0:
  69. manager.renameCachedProfile(id, strings.TrimSpace(iccid), nickname)
  70. return nil
  71. case 1:
  72. return fmt.Errorf("%w (result=0x%02X)", ErrESIMNicknameProfileNotFound, result)
  73. default:
  74. return fmt.Errorf("esim: eUICC rejected SetNickname, result=0x%02X (raw %s)", result, strings.ToUpper(hex.EncodeToString(payload)))
  75. }
  76. }