// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package key import ( "crypto/subtle" "encoding/hex" "encoding/json" "errors" "fmt" "strings" "go4.org/mem" "tailscale.com/types/structs" ) var ErrInvalidMeshKey = errors.New("invalid mesh key") // DERPMesh is a mesh key, used for inter-DERP-node communication and for // privileged DERP clients. type DERPMesh struct { _ structs.Incomparable // == isn't constant-time k [32]byte // 64-digit hexadecimal numbers fit in 32 bytes } func (k DERPMesh) MarshalJSON() ([]byte, error) { return json.Marshal(k.String()) } func (k *DERPMesh) UnmarshalJSON(data []byte) error { var s string json.Unmarshal(data, &s) if len(s) != 64 { return fmt.Errorf("%w: JSON Unmarshal failure, mesh key must be a 64-digit hexadecimal number", ErrInvalidMeshKey) } decoded, err := hex.DecodeString(s) if err != nil { return fmt.Errorf("%w: JSON Unmarshal failure, %v", ErrInvalidMeshKey, err) } if len(decoded) != 32 { return fmt.Errorf("%w: JSON Unmarshal failure, mesh key must be 32 bytes", ErrInvalidMeshKey) } copy(k.k[:], decoded) return nil } // DERPMeshFromRaw32 parses a 32-byte raw value as a DERP mesh key. func DERPMeshFromRaw32(raw mem.RO) (DERPMesh, error) { if raw.Len() != 32 { return DERPMesh{}, fmt.Errorf("%w: must be 32 bytes", ErrInvalidMeshKey) } var ret DERPMesh raw.Copy(ret.k[:]) return ret, nil } // ParseDERPMesh parses a DERP mesh key from a string. // This function trims whitespace around the string. // If the key is not a 64-digit hexadecimal number, ErrInvalidMeshKey is returned. func ParseDERPMesh(key string) (DERPMesh, error) { key = strings.TrimSpace(key) if len(key) != 64 { return DERPMesh{}, fmt.Errorf("%w: must be 64-digit hexadecimal number", ErrInvalidMeshKey) } decoded, err := hex.DecodeString(key) if err != nil { return DERPMesh{}, fmt.Errorf("%w: %v", ErrInvalidMeshKey, err) } return DERPMeshFromRaw32(mem.B(decoded)) } // IsZero reports whether k is the zero value. func (k DERPMesh) IsZero() bool { return k.Equal(DERPMesh{}) } // Equal reports whether k and other are the same key. func (k DERPMesh) Equal(other DERPMesh) bool { // Compare mesh keys in constant time to prevent timing attacks. // Since mesh keys are a fixed length, we don’t need to be concerned // about timing attacks on client mesh keys that are the wrong length. // See https://github.com/tailscale/corp/issues/28720 return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1 } // String returns k as a hex-encoded 64-digit number. func (k DERPMesh) String() string { return hex.EncodeToString(k.k[:]) }