deviceid.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright (C) 2014 The Protocol Authors.
  2. package protocol
  3. import (
  4. "bytes"
  5. "crypto/sha256"
  6. "encoding/base32"
  7. "errors"
  8. "fmt"
  9. "regexp"
  10. "strings"
  11. "github.com/calmh/luhn"
  12. )
  13. type DeviceID [32]byte
  14. var LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
  15. // NewDeviceID generates a new device ID from the raw bytes of a certificate
  16. func NewDeviceID(rawCert []byte) DeviceID {
  17. var n DeviceID
  18. hf := sha256.New()
  19. hf.Write(rawCert)
  20. hf.Sum(n[:0])
  21. return n
  22. }
  23. func DeviceIDFromString(s string) (DeviceID, error) {
  24. var n DeviceID
  25. err := n.UnmarshalText([]byte(s))
  26. return n, err
  27. }
  28. func DeviceIDFromBytes(bs []byte) DeviceID {
  29. var n DeviceID
  30. if len(bs) != len(n) {
  31. panic("incorrect length of byte slice representing device ID")
  32. }
  33. copy(n[:], bs)
  34. return n
  35. }
  36. // String returns the canonical string representation of the device ID
  37. func (n DeviceID) String() string {
  38. id := base32.StdEncoding.EncodeToString(n[:])
  39. id = strings.Trim(id, "=")
  40. id, err := luhnify(id)
  41. if err != nil {
  42. // Should never happen
  43. panic(err)
  44. }
  45. id = chunkify(id)
  46. return id
  47. }
  48. func (n DeviceID) GoString() string {
  49. return n.String()
  50. }
  51. func (n DeviceID) Compare(other DeviceID) int {
  52. return bytes.Compare(n[:], other[:])
  53. }
  54. func (n DeviceID) Equals(other DeviceID) bool {
  55. return bytes.Compare(n[:], other[:]) == 0
  56. }
  57. func (n *DeviceID) MarshalText() ([]byte, error) {
  58. return []byte(n.String()), nil
  59. }
  60. func (n *DeviceID) UnmarshalText(bs []byte) error {
  61. id := string(bs)
  62. id = strings.Trim(id, "=")
  63. id = strings.ToUpper(id)
  64. id = untypeoify(id)
  65. id = unchunkify(id)
  66. var err error
  67. switch len(id) {
  68. case 56:
  69. // New style, with check digits
  70. id, err = unluhnify(id)
  71. if err != nil {
  72. return err
  73. }
  74. fallthrough
  75. case 52:
  76. // Old style, no check digits
  77. dec, err := base32.StdEncoding.DecodeString(id + "====")
  78. if err != nil {
  79. return err
  80. }
  81. copy(n[:], dec)
  82. return nil
  83. default:
  84. return errors.New("device ID invalid: incorrect length")
  85. }
  86. }
  87. func luhnify(s string) (string, error) {
  88. if len(s) != 52 {
  89. panic("unsupported string length")
  90. }
  91. res := make([]string, 0, 4)
  92. for i := 0; i < 4; i++ {
  93. p := s[i*13 : (i+1)*13]
  94. l, err := luhn.Base32.Generate(p)
  95. if err != nil {
  96. return "", err
  97. }
  98. res = append(res, fmt.Sprintf("%s%c", p, l))
  99. }
  100. return res[0] + res[1] + res[2] + res[3], nil
  101. }
  102. func unluhnify(s string) (string, error) {
  103. if len(s) != 56 {
  104. return "", fmt.Errorf("unsupported string length %d", len(s))
  105. }
  106. res := make([]string, 0, 4)
  107. for i := 0; i < 4; i++ {
  108. p := s[i*14 : (i+1)*14-1]
  109. l, err := luhn.Base32.Generate(p)
  110. if err != nil {
  111. return "", err
  112. }
  113. if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
  114. return "", errors.New("check digit incorrect")
  115. }
  116. res = append(res, p)
  117. }
  118. return res[0] + res[1] + res[2] + res[3], nil
  119. }
  120. func chunkify(s string) string {
  121. s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
  122. s = strings.Trim(s, "-")
  123. return s
  124. }
  125. func unchunkify(s string) string {
  126. s = strings.Replace(s, "-", "", -1)
  127. s = strings.Replace(s, " ", "", -1)
  128. return s
  129. }
  130. func untypeoify(s string) string {
  131. s = strings.Replace(s, "0", "O", -1)
  132. s = strings.Replace(s, "1", "I", -1)
  133. s = strings.Replace(s, "8", "B", -1)
  134. return s
  135. }