Files
tailscale/types/key/machine.go
T
Will Norris 3ec5be3f51 all: remove AUTHORS file and references to it
This file was never truly necessary and has never actually been used in
the history of Tailscale's open source releases.

A Brief History of AUTHORS files
---

The AUTHORS file was a pattern developed at Google, originally for
Chromium, then adopted by Go and a bunch of other projects. The problem
was that Chromium originally had a copyright line only recognizing
Google as the copyright holder. Because Google (and most open source
projects) do not require copyright assignemnt for contributions, each
contributor maintains their copyright. Some large corporate contributors
then tried to add their own name to the copyright line in the LICENSE
file or in file headers. This quickly becomes unwieldy, and puts a
tremendous burden on anyone building on top of Chromium, since the
license requires that they keep all copyright lines intact.

The compromise was to create an AUTHORS file that would list all of the
copyright holders. The LICENSE file and source file headers would then
include that list by reference, listing the copyright holder as "The
Chromium Authors".

This also become cumbersome to simply keep the file up to date with a
high rate of new contributors. Plus it's not always obvious who the
copyright holder is. Sometimes it is the individual making the
contribution, but many times it may be their employer. There is no way
for the proejct maintainer to know.

Eventually, Google changed their policy to no longer recommend trying to
keep the AUTHORS file up to date proactively, and instead to only add to
it when requested: https://opensource.google/docs/releasing/authors.
They are also clear that:

> Adding contributors to the AUTHORS file is entirely within the
> project's discretion and has no implications for copyright ownership.

It was primarily added to appease a small number of large contributors
that insisted that they be recognized as copyright holders (which was
entirely their right to do). But it's not truly necessary, and not even
the most accurate way of identifying contributors and/or copyright
holders.

In practice, we've never added anyone to our AUTHORS file. It only lists
Tailscale, so it's not really serving any purpose. It also causes
confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header
in other open source repos which don't actually have an AUTHORS file, so
it's ambiguous what that means.

Instead, we just acknowledge that the contributors to Tailscale (whoever
they are) are copyright holders for their individual contributions. We
also have the benefit of using the DCO (developercertificate.org) which
provides some additional certification of their right to make the
contribution.

The source file changes were purely mechanical with:

    git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g'

Updates #cleanup

Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d
Signed-off-by: Will Norris <will@tailscale.com>
2026-01-23 15:49:45 -08:00

265 lines
8.1 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// 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))
}