deviceid.go 3.5 KB

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