nodeid.go 3.4 KB

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