machine.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package key
  4. import (
  5. "bytes"
  6. "crypto/subtle"
  7. "encoding/hex"
  8. "go4.org/mem"
  9. "golang.org/x/crypto/curve25519"
  10. "golang.org/x/crypto/nacl/box"
  11. "tailscale.com/types/structs"
  12. )
  13. const (
  14. // machinePrivateHexPrefix is the prefix used to identify a
  15. // hex-encoded machine private key.
  16. //
  17. // This prefix name is a little unfortunate, in that it comes from
  18. // WireGuard's own key types. Unfortunately we're stuck with it for
  19. // machine keys, because we serialize them to disk with this prefix.
  20. machinePrivateHexPrefix = "privkey:"
  21. // machinePublicHexPrefix is the prefix used to identify a
  22. // hex-encoded machine public key.
  23. //
  24. // This prefix is used in the control protocol, so cannot be
  25. // changed.
  26. machinePublicHexPrefix = "mkey:"
  27. )
  28. // MachinePrivate is a machine key, used for communication with the
  29. // Tailscale coordination server.
  30. type MachinePrivate struct {
  31. _ structs.Incomparable // == isn't constant-time
  32. k [32]byte
  33. }
  34. // NewMachine creates and returns a new machine private key.
  35. func NewMachine() MachinePrivate {
  36. var ret MachinePrivate
  37. rand(ret.k[:])
  38. clamp25519Private(ret.k[:])
  39. return ret
  40. }
  41. // IsZero reports whether k is the zero value.
  42. func (k MachinePrivate) IsZero() bool {
  43. return k.Equal(MachinePrivate{})
  44. }
  45. // Equal reports whether k and other are the same key.
  46. func (k MachinePrivate) Equal(other MachinePrivate) bool {
  47. return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
  48. }
  49. // Public returns the MachinePublic for k.
  50. // Panics if MachinePrivate is zero.
  51. func (k MachinePrivate) Public() MachinePublic {
  52. if k.IsZero() {
  53. panic("can't take the public key of a zero MachinePrivate")
  54. }
  55. var ret MachinePublic
  56. curve25519.ScalarBaseMult(&ret.k, &k.k)
  57. return ret
  58. }
  59. // AppendText implements encoding.TextAppender.
  60. func (k MachinePrivate) AppendText(b []byte) ([]byte, error) {
  61. return appendHexKey(b, machinePrivateHexPrefix, k.k[:]), nil
  62. }
  63. // MarshalText implements encoding.TextMarshaler.
  64. func (k MachinePrivate) MarshalText() ([]byte, error) {
  65. return k.AppendText(nil)
  66. }
  67. // MarshalText implements encoding.TextUnmarshaler.
  68. func (k *MachinePrivate) UnmarshalText(b []byte) error {
  69. return parseHex(k.k[:], mem.B(b), mem.S(machinePrivateHexPrefix))
  70. }
  71. // UntypedBytes returns k, encoded as an untyped 64-character hex
  72. // string.
  73. //
  74. // Deprecated: this function is risky to use, because it produces
  75. // serialized values that do not identify themselves as a
  76. // MachinePrivate, allowing other code to potentially parse it back in
  77. // as the wrong key type. For new uses that don't require this
  78. // specific raw byte serialization, please use
  79. // MarshalText/UnmarshalText.
  80. func (k MachinePrivate) UntypedBytes() []byte {
  81. return bytes.Clone(k.k[:])
  82. }
  83. // SealTo wraps cleartext into a NaCl box (see
  84. // golang.org/x/crypto/nacl) to p, authenticated from k, using a
  85. // random nonce.
  86. //
  87. // The returned ciphertext is a 24-byte nonce concatenated with the
  88. // box value.
  89. func (k MachinePrivate) SealTo(p MachinePublic, cleartext []byte) (ciphertext []byte) {
  90. if k.IsZero() || p.IsZero() {
  91. panic("can't seal with zero keys")
  92. }
  93. var nonce [24]byte
  94. rand(nonce[:])
  95. return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k)
  96. }
  97. // SharedKey returns the precomputed Nacl box shared key between k and p.
  98. func (k MachinePrivate) SharedKey(p MachinePublic) MachinePrecomputedSharedKey {
  99. var shared MachinePrecomputedSharedKey
  100. box.Precompute(&shared.k, &p.k, &k.k)
  101. return shared
  102. }
  103. // MachinePrecomputedSharedKey is a precomputed shared NaCl box shared key.
  104. type MachinePrecomputedSharedKey struct {
  105. k [32]byte
  106. }
  107. // Seal wraps cleartext into a NaCl box (see
  108. // golang.org/x/crypto/nacl) using the shared key k as generated
  109. // by MachinePrivate.SharedKey.
  110. //
  111. // The returned ciphertext is a 24-byte nonce concatenated with the
  112. // box value.
  113. func (k MachinePrecomputedSharedKey) Seal(cleartext []byte) (ciphertext []byte) {
  114. if k == (MachinePrecomputedSharedKey{}) {
  115. panic("can't seal with zero keys")
  116. }
  117. var nonce [24]byte
  118. rand(nonce[:])
  119. return box.SealAfterPrecomputation(nonce[:], cleartext, &nonce, &k.k)
  120. }
  121. // Open opens the NaCl box ciphertext, which must be a value created by
  122. // MachinePrecomputedSharedKey.Seal or MachinePrivate.SealTo, and returns the
  123. // inner cleartext if ciphertext is a valid box for the shared key k.
  124. func (k MachinePrecomputedSharedKey) Open(ciphertext []byte) (cleartext []byte, ok bool) {
  125. if k == (MachinePrecomputedSharedKey{}) {
  126. panic("can't open with zero keys")
  127. }
  128. if len(ciphertext) < 24 {
  129. return nil, false
  130. }
  131. var nonce [24]byte
  132. copy(nonce[:], ciphertext)
  133. return box.OpenAfterPrecomputation(nil, ciphertext[len(nonce):], &nonce, &k.k)
  134. }
  135. // OpenFrom opens the NaCl box ciphertext, which must be a value
  136. // created by SealTo, and returns the inner cleartext if ciphertext is
  137. // a valid box from p to k.
  138. func (k MachinePrivate) OpenFrom(p MachinePublic, ciphertext []byte) (cleartext []byte, ok bool) {
  139. if k.IsZero() || p.IsZero() {
  140. panic("can't open with zero keys")
  141. }
  142. if len(ciphertext) < 24 {
  143. return nil, false
  144. }
  145. var nonce [24]byte
  146. copy(nonce[:], ciphertext)
  147. return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k)
  148. }
  149. // MachinePublic is the public portion of a a MachinePrivate.
  150. type MachinePublic struct {
  151. k [32]byte
  152. }
  153. // MachinePublicFromRaw32 parses a 32-byte raw value as a MachinePublic.
  154. //
  155. // This should be used only when deserializing a MachinePublic from a
  156. // binary protocol.
  157. func MachinePublicFromRaw32(raw mem.RO) MachinePublic {
  158. if raw.Len() != 32 {
  159. panic("input has wrong size")
  160. }
  161. var ret MachinePublic
  162. raw.Copy(ret.k[:])
  163. return ret
  164. }
  165. // ParseMachinePublicUntyped parses an untyped 64-character hex value
  166. // as a MachinePublic.
  167. //
  168. // Deprecated: this function is risky to use, because it cannot verify
  169. // that the hex string was intended to be a MachinePublic. This can
  170. // lead to accidentally decoding one type of key as another. For new
  171. // uses that don't require backwards compatibility with the untyped
  172. // string format, please use MarshalText/UnmarshalText.
  173. func ParseMachinePublicUntyped(raw mem.RO) (MachinePublic, error) {
  174. var ret MachinePublic
  175. if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil {
  176. return MachinePublic{}, err
  177. }
  178. return ret, nil
  179. }
  180. // IsZero reports whether k is the zero value.
  181. func (k MachinePublic) IsZero() bool {
  182. return k == MachinePublic{}
  183. }
  184. // ShortString returns the Tailscale conventional debug representation
  185. // of a public key: the first five base64 digits of the key, in square
  186. // brackets.
  187. func (k MachinePublic) ShortString() string {
  188. return debug32(k.k)
  189. }
  190. // UntypedHexString returns k, encoded as an untyped 64-character hex
  191. // string.
  192. //
  193. // Deprecated: this function is risky to use, because it produces
  194. // serialized values that do not identify themselves as a
  195. // MachinePublic, allowing other code to potentially parse it back in
  196. // as the wrong key type. For new uses that don't require backwards
  197. // compatibility with the untyped string format, please use
  198. // MarshalText/UnmarshalText.
  199. func (k MachinePublic) UntypedHexString() string {
  200. return hex.EncodeToString(k.k[:])
  201. }
  202. // UntypedBytes returns k, encoded as an untyped 64-character hex
  203. // string.
  204. //
  205. // Deprecated: this function is risky to use, because it produces
  206. // serialized values that do not identify themselves as a
  207. // MachinePublic, allowing other code to potentially parse it back in
  208. // as the wrong key type. For new uses that don't require this
  209. // specific raw byte serialization, please use
  210. // MarshalText/UnmarshalText.
  211. func (k MachinePublic) UntypedBytes() []byte {
  212. return bytes.Clone(k.k[:])
  213. }
  214. // String returns the output of MarshalText as a string.
  215. func (k MachinePublic) String() string {
  216. bs, err := k.MarshalText()
  217. if err != nil {
  218. panic(err)
  219. }
  220. return string(bs)
  221. }
  222. // AppendText implements encoding.TextAppender.
  223. func (k MachinePublic) AppendText(b []byte) ([]byte, error) {
  224. return appendHexKey(b, machinePublicHexPrefix, k.k[:]), nil
  225. }
  226. // MarshalText implements encoding.TextMarshaler.
  227. func (k MachinePublic) MarshalText() ([]byte, error) {
  228. return k.AppendText(nil)
  229. }
  230. // MarshalText implements encoding.TextUnmarshaler.
  231. func (k *MachinePublic) UnmarshalText(b []byte) error {
  232. return parseHex(k.k[:], mem.B(b), mem.S(machinePublicHexPrefix))
  233. }