deviceid.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright (C) 2014 The Protocol Authors.
  2. package protocol
  3. import (
  4. "bytes"
  5. "encoding/base32"
  6. "encoding/binary"
  7. "errors"
  8. "fmt"
  9. "strings"
  10. "github.com/syncthing/syncthing/lib/sha256"
  11. )
  12. const (
  13. DeviceIDLength = 32
  14. ShortIDStringLength = 7
  15. )
  16. type (
  17. DeviceID [DeviceIDLength]byte
  18. ShortID uint64
  19. )
  20. var (
  21. LocalDeviceID = repeatedDeviceID(0xff)
  22. GlobalDeviceID = repeatedDeviceID(0xf8)
  23. EmptyDeviceID = DeviceID{ /* all zeroes */ }
  24. )
  25. func repeatedDeviceID(v byte) (d DeviceID) {
  26. for i := range d {
  27. d[i] = v
  28. }
  29. return
  30. }
  31. // NewDeviceID generates a new device ID from the raw bytes of a certificate
  32. func NewDeviceID(rawCert []byte) DeviceID {
  33. return DeviceID(sha256.Sum256(rawCert))
  34. }
  35. func DeviceIDFromString(s string) (DeviceID, error) {
  36. var n DeviceID
  37. err := n.UnmarshalText([]byte(s))
  38. return n, err
  39. }
  40. func DeviceIDFromBytes(bs []byte) (DeviceID, error) {
  41. var n DeviceID
  42. if len(bs) != len(n) {
  43. return n, errors.New("incorrect length of byte slice representing device ID")
  44. }
  45. copy(n[:], bs)
  46. return n, nil
  47. }
  48. // String returns the canonical string representation of the device ID
  49. func (n DeviceID) String() string {
  50. if n == EmptyDeviceID {
  51. return ""
  52. }
  53. id := base32.StdEncoding.EncodeToString(n[:])
  54. id = strings.Trim(id, "=")
  55. id, err := luhnify(id)
  56. if err != nil {
  57. // Should never happen
  58. panic(err)
  59. }
  60. id = chunkify(id)
  61. return id
  62. }
  63. func (n DeviceID) GoString() string {
  64. return n.String()
  65. }
  66. func (n DeviceID) Compare(other DeviceID) int {
  67. return bytes.Compare(n[:], other[:])
  68. }
  69. func (n DeviceID) Equals(other DeviceID) bool {
  70. return bytes.Equal(n[:], other[:])
  71. }
  72. // Short returns an integer representing bits 0-63 of the device ID.
  73. func (n DeviceID) Short() ShortID {
  74. return ShortID(binary.BigEndian.Uint64(n[:]))
  75. }
  76. func (n DeviceID) MarshalText() ([]byte, error) {
  77. return []byte(n.String()), nil
  78. }
  79. func (s ShortID) String() string {
  80. if s == 0 {
  81. return ""
  82. }
  83. var bs [8]byte
  84. binary.BigEndian.PutUint64(bs[:], uint64(s))
  85. return base32.StdEncoding.EncodeToString(bs[:])[:ShortIDStringLength]
  86. }
  87. func (n *DeviceID) UnmarshalText(bs []byte) error {
  88. id := string(bs)
  89. id = strings.Trim(id, "=")
  90. id = strings.ToUpper(id)
  91. id = untypeoify(id)
  92. id = unchunkify(id)
  93. var err error
  94. switch len(id) {
  95. case 0:
  96. *n = EmptyDeviceID
  97. return nil
  98. case 56:
  99. // New style, with check digits
  100. id, err = unluhnify(id)
  101. if err != nil {
  102. return err
  103. }
  104. fallthrough
  105. case 52:
  106. // Old style, no check digits
  107. dec, err := base32.StdEncoding.DecodeString(id + "====")
  108. if err != nil {
  109. return err
  110. }
  111. copy(n[:], dec)
  112. return nil
  113. default:
  114. return fmt.Errorf("%q: device ID invalid: incorrect length", bs)
  115. }
  116. }
  117. func (*DeviceID) ProtoSize() int {
  118. // Used by protobuf marshaller.
  119. return DeviceIDLength
  120. }
  121. func (n *DeviceID) MarshalTo(bs []byte) (int, error) {
  122. // Used by protobuf marshaller.
  123. if len(bs) < DeviceIDLength {
  124. return 0, errors.New("destination too short")
  125. }
  126. copy(bs, (*n)[:])
  127. return DeviceIDLength, nil
  128. }
  129. func (n *DeviceID) Unmarshal(bs []byte) error {
  130. // Used by protobuf marshaller.
  131. if len(bs) < DeviceIDLength {
  132. return fmt.Errorf("%q: not enough data", bs)
  133. }
  134. copy((*n)[:], bs)
  135. return nil
  136. }
  137. func luhnify(s string) (string, error) {
  138. if len(s) != 52 {
  139. panic("unsupported string length")
  140. }
  141. res := make([]byte, 4*(13+1))
  142. for i := 0; i < 4; i++ {
  143. p := s[i*13 : (i+1)*13]
  144. copy(res[i*(13+1):], p)
  145. l, err := luhn32(p)
  146. if err != nil {
  147. return "", err
  148. }
  149. res[(i+1)*(13)+i] = byte(l)
  150. }
  151. return string(res), nil
  152. }
  153. func unluhnify(s string) (string, error) {
  154. if len(s) != 56 {
  155. return "", fmt.Errorf("%q: unsupported string length %d", s, len(s))
  156. }
  157. res := make([]byte, 52)
  158. for i := 0; i < 4; i++ {
  159. p := s[i*(13+1) : (i+1)*(13+1)-1]
  160. copy(res[i*13:], p)
  161. l, err := luhn32(p)
  162. if err != nil {
  163. return "", err
  164. }
  165. if s[(i+1)*14-1] != byte(l) {
  166. return "", fmt.Errorf("%q: check digit incorrect", s)
  167. }
  168. }
  169. return string(res), nil
  170. }
  171. func chunkify(s string) string {
  172. chunks := len(s) / 7
  173. res := make([]byte, chunks*(7+1)-1)
  174. for i := 0; i < chunks; i++ {
  175. if i > 0 {
  176. res[i*(7+1)-1] = '-'
  177. }
  178. copy(res[i*(7+1):], s[i*7:(i+1)*7])
  179. }
  180. return string(res)
  181. }
  182. func unchunkify(s string) string {
  183. s = strings.ReplaceAll(s, "-", "")
  184. s = strings.ReplaceAll(s, " ", "")
  185. return s
  186. }
  187. func untypeoify(s string) string {
  188. s = strings.ReplaceAll(s, "0", "O")
  189. s = strings.ReplaceAll(s, "1", "I")
  190. s = strings.ReplaceAll(s, "8", "B")
  191. return s
  192. }