deviceid.go 5.1 KB

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