derp.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package key
  4. import (
  5. "crypto/subtle"
  6. "encoding/hex"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "go4.org/mem"
  12. "tailscale.com/types/structs"
  13. )
  14. var ErrInvalidMeshKey = errors.New("invalid mesh key")
  15. // DERPMesh is a mesh key, used for inter-DERP-node communication and for
  16. // privileged DERP clients.
  17. type DERPMesh struct {
  18. _ structs.Incomparable // == isn't constant-time
  19. k [32]byte // 64-digit hexadecimal numbers fit in 32 bytes
  20. }
  21. // MarshalJSON implements the [encoding/json.Marshaler] interface.
  22. func (k DERPMesh) MarshalJSON() ([]byte, error) {
  23. return json.Marshal(k.String())
  24. }
  25. // UnmarshalJSON implements the [encoding/json.Unmarshaler] interface.
  26. func (k *DERPMesh) UnmarshalJSON(data []byte) error {
  27. var s string
  28. json.Unmarshal(data, &s)
  29. if hex.DecodedLen(len(s)) != len(k.k) {
  30. return fmt.Errorf("types/key/derp: cannot unmarshal, incorrect size mesh key len: %d, must be %d, %w", hex.DecodedLen(len(s)), len(k.k), ErrInvalidMeshKey)
  31. }
  32. _, err := hex.Decode(k.k[:], []byte(s))
  33. if err != nil {
  34. return fmt.Errorf("types/key/derp: cannot unmarshal, invalid mesh key: %w", err)
  35. }
  36. return nil
  37. }
  38. // DERPMeshFromRaw32 parses a 32-byte raw value as a DERP mesh key.
  39. func DERPMeshFromRaw32(raw mem.RO) DERPMesh {
  40. if raw.Len() != 32 {
  41. panic("input has wrong size")
  42. }
  43. var ret DERPMesh
  44. raw.Copy(ret.k[:])
  45. return ret
  46. }
  47. // ParseDERPMesh parses a DERP mesh key from a string.
  48. // This function trims whitespace around the string.
  49. // If the key is not a 64-digit hexadecimal number, ErrInvalidMeshKey is returned.
  50. func ParseDERPMesh(key string) (DERPMesh, error) {
  51. key = strings.TrimSpace(key)
  52. if len(key) != 64 {
  53. return DERPMesh{}, fmt.Errorf("%w: must be 64-digit hexadecimal number", ErrInvalidMeshKey)
  54. }
  55. decoded, err := hex.DecodeString(key)
  56. if err != nil {
  57. return DERPMesh{}, fmt.Errorf("%w: %v", ErrInvalidMeshKey, err)
  58. }
  59. return DERPMeshFromRaw32(mem.B(decoded)), nil
  60. }
  61. // IsZero reports whether k is the zero value.
  62. func (k DERPMesh) IsZero() bool {
  63. return k.Equal(DERPMesh{})
  64. }
  65. // Equal reports whether k and other are the same key.
  66. func (k DERPMesh) Equal(other DERPMesh) bool {
  67. // Compare mesh keys in constant time to prevent timing attacks.
  68. // Since mesh keys are a fixed length, we don’t need to be concerned
  69. // about timing attacks on client mesh keys that are the wrong length.
  70. // See https://github.com/tailscale/corp/issues/28720
  71. return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
  72. }
  73. // String returns k as a hex-encoded 64-digit number.
  74. func (k DERPMesh) String() string {
  75. return hex.EncodeToString(k.k[:])
  76. }