Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
package key
import ( "bufio" "bytes" "crypto/subtle" "encoding/hex" "errors" "fmt"
"go4.org/mem" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/nacl/box" "tailscale.com/types/structs" "tailscale.com/util/bufiox")
const ( // nodePrivateHexPrefix is the prefix used to identify a // hex-encoded node private key. // // This prefix name is a little unfortunate, in that it comes from // WireGuard's own key types, and we've used it for both key types // we persist to disk (machine and node keys). But we're stuck // with it for now, barring another round of tricky migration. nodePrivateHexPrefix = "privkey:"
// nodePublicHexPrefix is the prefix used to identify a // hex-encoded node public key. // // This prefix is used in the control protocol, so cannot be // changed. nodePublicHexPrefix = "nodekey:"
// nodePublicBinaryPrefix is the prefix used to identify a // binary-encoded node public key. nodePublicBinaryPrefix = "np"
// NodePublicRawLen is the length in bytes of a NodePublic, when // serialized with AppendTo, Raw32 or WriteRawWithoutAllocating. NodePublicRawLen = 32)
// NodePrivate is a node key, used for WireGuard tunnels and// communication with DERP servers.type NodePrivate struct { _ structs.Incomparable // because == isn't constant-time k [32]byte}
// NewNode creates and returns a new node private key.func NewNode() NodePrivate { var ret NodePrivate rand(ret.k[:]) // WireGuard does its own clamping, so this would be unnecessary - // but we also use this key for DERP comms, which does require // clamping. clamp25519Private(ret.k[:]) return ret}
// Raw32 returns k as 32 raw bytes.func (k NodePrivate) Raw32() [32]byte { return k.k }
// NodePrivateAs returns a NodePrivate as a named fixed-size array of bytes.// It's intended for interoperability with wireguard-go's// device.NoisePrivateKey type.func NodePrivateAs[T ~[32]byte](k NodePrivate) T { return k.k }
// NodePrivateFromRaw32 parses a 32-byte raw value as a NodePrivate.//// Deprecated: only needed to cast from legacy node private key types,// do not add more uses unrelated to #3206.func NodePrivateFromRaw32(raw mem.RO) NodePrivate { if raw.Len() != 32 { panic("input has wrong size") } var ret NodePrivate raw.Copy(ret.k[:]) return ret}
func ParseNodePrivateUntyped(raw mem.RO) (NodePrivate, error) { var ret NodePrivate if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil { return NodePrivate{}, err } return ret, nil}
// IsZero reports whether k is the zero value.func (k NodePrivate) IsZero() bool { return k.Equal(NodePrivate{})}
// Equal reports whether k and other are the same key.func (k NodePrivate) Equal(other NodePrivate) bool { return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1}
// Public returns the NodePublic for k.// Panics if NodePrivate is zero.func (k NodePrivate) Public() NodePublic { if k.IsZero() { panic("can't take the public key of a zero NodePrivate") } var ret NodePublic curve25519.ScalarBaseMult(&ret.k, &k.k) return ret}
// AppendText implements encoding.TextAppender.func (k NodePrivate) AppendText(b []byte) ([]byte, error) { return appendHexKey(b, nodePrivateHexPrefix, k.k[:]), nil}
// MarshalText implements encoding.TextMarshaler.func (k NodePrivate) MarshalText() ([]byte, error) { return k.AppendText(nil)}
// MarshalText implements encoding.TextUnmarshaler.func (k *NodePrivate) UnmarshalText(b []byte) error { return parseHex(k.k[:], mem.B(b), mem.S(nodePrivateHexPrefix))}
// 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 NodePrivate) SealTo(p NodePublic, 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)}
// 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 NodePrivate) OpenFrom(p NodePublic, 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 } nonce := (*[24]byte)(ciphertext) return box.Open(nil, ciphertext[len(nonce):], nonce, &p.k, &k.k)}
// UntypedHexString returns k, encoded as an untyped 64-character hex// string.//// Warning: this function is risky to use, because it produces// serialized values that do not identify themselves as a// NodePrivate, 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 NodePrivate) UntypedHexString() string { return hex.EncodeToString(k.k[:])}
// NodePublic is the public portion of a NodePrivate.type NodePublic struct { k [32]byte}
// Shard returns a uint8 number from a public key with// mostly-uniform distribution, suitable for sharding.func (p NodePublic) Shard() uint8 { // A 25519 public key isn't uniformly random, as it ultimately // corresponds to a point on the curve. // But we don't need perfectly uniformly-random, we need // good-enough-for-sharding random, so we haphazardly // combine raw values of the key to give us something sufficient. s := uint8(p.k[31]) + uint8(p.k[30]) + uint8(p.k[20]) return s ^ uint8(p.k[2]+p.k[12])}
// Compare returns -1, 0, or 1, depending on whether p orders before p2,// using bytes.Compare on the bytes of the public key.func (p NodePublic) Compare(p2 NodePublic) int { return bytes.Compare(p.k[:], p2.k[:])}
// ParseNodePublicUntyped parses an untyped 64-character hex value// as a NodePublic.//// Deprecated: this function is risky to use, because it cannot verify// that the hex string was intended to be a NodePublic. 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 ParseNodePublicUntyped(raw mem.RO) (NodePublic, error) { var ret NodePublic if err := parseHex(ret.k[:], raw, mem.B(nil)); err != nil { return NodePublic{}, err } return ret, nil}
// NodePublicFromRaw32 parses a 32-byte raw value as a NodePublic.//// This should be used only when deserializing a NodePublic from a// binary protocol.func NodePublicFromRaw32(raw mem.RO) NodePublic { if raw.Len() != 32 { panic("input has wrong size") } var ret NodePublic raw.Copy(ret.k[:]) return ret}
// badOldPrefix is a nodekey/discokey prefix that, when base64'd, serializes// with a "bad01" ("bad ol'", ~"bad old") prefix. It's used for expired node// keys so when we debug a customer issue, the "bad01" can jump out to us. See://// https://github.com/tailscale/tailscale/issues/6932var badOldPrefix = []byte{109, 167, 116, 213, 215, 116}
// NodePublicWithBadOldPrefix returns a copy of k with its leading public key// bytes mutated such that it base64's to a ShortString of [bad01] ("bad ol'"// [expired node key]).func NodePublicWithBadOldPrefix(k NodePublic) NodePublic { var buf [32]byte k.AppendTo(buf[:0]) copy(buf[:], badOldPrefix) return NodePublicFromRaw32(mem.B(buf[:]))}
// IsZero reports whether k is the zero value.func (k NodePublic) IsZero() bool { return k == NodePublic{}}
// ShortString returns the Tailscale conventional debug representation// of a public key: the first five base64 digits of the key, in square// brackets.func (k NodePublic) ShortString() string { return debug32(k.k)}
// AppendTo appends k, serialized as a 32-byte binary value, to// buf. Returns the new slice.func (k NodePublic) AppendTo(buf []byte) []byte { return append(buf, k.k[:]...)}
// ReadRawWithoutAllocating initializes k with bytes read from br.// It uses [bufiox.ReadFull] to read without heap allocations.func (k *NodePublic) ReadRawWithoutAllocating(br *bufio.Reader) error { var z NodePublic if *k != z { return errors.New("refusing to read into non-zero NodePublic") } _, err := bufiox.ReadFull(br, k.k[:]) return err}
// WriteRawWithoutAllocating writes out k as 32 big-endian bytes to bw.//// It uses AvailableBuffer to append directly into bufio's internal// buffer without allocation, falling back to WriteByte when the// buffer has insufficient space.func (k NodePublic) WriteRawWithoutAllocating(bw *bufio.Writer) error { // Fast path: enough space in the buffer to append directly. if bw.Available() >= len(k.k) { buf := bw.AvailableBuffer() buf = append(buf, k.k[:]...) _, err := bw.Write(buf) return err } // Slow path: buffer nearly full. Write byte-at-a-time to let // bufio flush as needed, avoiding a heap allocation from append // growing past AvailableBuffer's capacity. for _, b := range k.k { if err := bw.WriteByte(b); err != nil { return err } } return nil}
// Raw32 returns k encoded as 32 raw bytes.//// Deprecated: only needed for a single legacy use in the control// server and a few places in the wireguard-go API; don't add// more uses.func (k NodePublic) Raw32() [32]byte { return k.k}
// Less reports whether k orders before other, using an undocumented// deterministic ordering.func (k NodePublic) Less(other NodePublic) bool { return bytes.Compare(k.k[:], other.k[:]) < 0}
// UntypedHexString returns k, encoded as an untyped 64-character hex// string.//// Warning: this function is risky to use, because it produces// serialized values that do not identify themselves as a// NodePublic, 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 NodePublic) UntypedHexString() string { return hex.EncodeToString(k.k[:])}
// String returns k as a hex-encoded string with a type prefix.func (k NodePublic) String() string { bs, err := k.MarshalText() if err != nil { panic(err) } return string(bs)}
// AppendText implements encoding.TextAppender. It appends a typed prefix// followed by hex encoded represtation of k to b.func (k NodePublic) AppendText(b []byte) ([]byte, error) { return appendHexKey(b, nodePublicHexPrefix, k.k[:]), nil}
// MarshalText implements encoding.TextMarshaler. It returns a typed prefix// followed by a hex encoded representation of k.func (k NodePublic) MarshalText() ([]byte, error) { return k.AppendText(nil)}
// UnmarshalText implements encoding.TextUnmarshaler. It expects a typed prefix// followed by a hex encoded representation of k.func (k *NodePublic) UnmarshalText(b []byte) error { return parseHex(k.k[:], mem.B(b), mem.S(nodePublicHexPrefix))}
// MarshalBinary implements encoding.BinaryMarshaler.func (k NodePublic) MarshalBinary() (data []byte, err error) { b := make([]byte, len(nodePublicBinaryPrefix)+NodePublicRawLen) copy(b[:len(nodePublicBinaryPrefix)], nodePublicBinaryPrefix) copy(b[len(nodePublicBinaryPrefix):], k.k[:]) return b, nil}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.func (k *NodePublic) UnmarshalBinary(in []byte) error { data := mem.B(in) if !mem.HasPrefix(data, mem.S(nodePublicBinaryPrefix)) { return fmt.Errorf("missing/incorrect type prefix %s", nodePublicBinaryPrefix) } if want, got := len(nodePublicBinaryPrefix)+NodePublicRawLen, data.Len(); want != got { return fmt.Errorf("incorrect len for NodePublic (%d != %d)", got, want) }
data.SliceFrom(len(nodePublicBinaryPrefix)).Copy(k.k[:]) return nil}
// WireGuardGoString prints k in the same format used by wireguard-go.func (k NodePublic) WireGuardGoString() string { // This implementation deliberately matches the overly complicated // implementation in wireguard-go. b64 := func(input byte) byte { return input + 'A' + byte(((25-int(input))>>8)&6) - byte(((51-int(input))>>8)&75) - byte(((61-int(input))>>8)&15) + byte(((62-int(input))>>8)&3) } b := []byte("peer(____…____)") const first = len("peer(") const second = len("peer(____…") b[first+0] = b64((k.k[0] >> 2) & 63) b[first+1] = b64(((k.k[0] << 4) | (k.k[1] >> 4)) & 63) b[first+2] = b64(((k.k[1] << 2) | (k.k[2] >> 6)) & 63) b[first+3] = b64(k.k[2] & 63) b[second+0] = b64(k.k[29] & 63) b[second+1] = b64((k.k[30] >> 2) & 63) b[second+2] = b64(((k.k[30] << 4) | (k.k[31] >> 4)) & 63) b[second+3] = b64((k.k[31] << 2) & 63) return string(b)}