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