2022-07-29 18:03:23 +00:00
|
|
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package key
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ed25519"
|
2022-08-01 22:46:41 +00:00
|
|
|
"crypto/subtle"
|
2022-07-29 18:03:23 +00:00
|
|
|
|
|
|
|
"go4.org/mem"
|
|
|
|
"tailscale.com/tka"
|
|
|
|
"tailscale.com/types/structs"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// nlPrivateHexPrefix is the prefix used to identify a
|
|
|
|
// hex-encoded network-lock key.
|
|
|
|
nlPrivateHexPrefix = "nlpriv:"
|
2022-08-01 18:03:48 +00:00
|
|
|
|
|
|
|
// nlPublicHexPrefix is the prefix used to identify the public
|
|
|
|
// side of a hex-encoded network-lock key.
|
|
|
|
nlPublicHexPrefix = "nlpub:"
|
2022-07-29 18:03:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// NLPrivate is a node-managed network-lock key, used for signing
|
|
|
|
// node-key signatures and authority update messages.
|
|
|
|
type NLPrivate struct {
|
|
|
|
_ structs.Incomparable // because == isn't constant-time
|
|
|
|
k [ed25519.PrivateKeySize]byte
|
|
|
|
}
|
|
|
|
|
2022-08-01 22:46:41 +00:00
|
|
|
// IsZero reports whether k is the zero value.
|
|
|
|
func (k NLPrivate) IsZero() bool {
|
|
|
|
empty := NLPrivate{}
|
|
|
|
return subtle.ConstantTimeCompare(k.k[:], empty.k[:]) == 1
|
|
|
|
}
|
|
|
|
|
2022-07-29 18:03:23 +00:00
|
|
|
// NewNLPrivate creates and returns a new network-lock key.
|
|
|
|
func NewNLPrivate() NLPrivate {
|
|
|
|
// ed25519.GenerateKey 'clamps' the key, not that it
|
|
|
|
// matters given we don't do Diffie-Hellman.
|
|
|
|
_, priv, err := ed25519.GenerateKey(nil) // nil == crypto/rand
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var out NLPrivate
|
|
|
|
copy(out.k[:], priv)
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalText implements encoding.TextUnmarshaler.
|
|
|
|
func (k *NLPrivate) UnmarshalText(b []byte) error {
|
|
|
|
return parseHex(k.k[:], mem.B(b), mem.S(nlPrivateHexPrefix))
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalText implements encoding.TextMarshaler.
|
|
|
|
func (k NLPrivate) MarshalText() ([]byte, error) {
|
|
|
|
return toHex(k.k[:], nlPrivateHexPrefix), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Public returns the public component of this key.
|
2022-08-01 18:03:48 +00:00
|
|
|
func (k NLPrivate) Public() NLPublic {
|
|
|
|
var out NLPublic
|
|
|
|
copy(out.k[:], ed25519.PrivateKey(k.k[:]).Public().(ed25519.PublicKey))
|
|
|
|
return out
|
2022-07-29 18:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// KeyID returns an identifier for this key.
|
|
|
|
func (k NLPrivate) KeyID() tka.KeyID {
|
2022-08-01 18:03:48 +00:00
|
|
|
pub := k.Public()
|
2022-07-29 18:03:23 +00:00
|
|
|
return tka.Key{
|
|
|
|
Kind: tka.Key25519,
|
2022-08-01 18:03:48 +00:00
|
|
|
Public: pub.k[:],
|
2022-07-29 18:03:23 +00:00
|
|
|
}.ID()
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignAUM implements tka.UpdateSigner.
|
|
|
|
func (k NLPrivate) SignAUM(a *tka.AUM) error {
|
|
|
|
sigHash := a.SigHash()
|
|
|
|
|
|
|
|
a.Signatures = append(a.Signatures, tka.Signature{
|
|
|
|
KeyID: k.KeyID(),
|
|
|
|
Signature: ed25519.Sign(k.k[:], sigHash[:]),
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-01 18:03:48 +00:00
|
|
|
|
|
|
|
// NLPublic is the public portion of a a NLPrivate.
|
|
|
|
type NLPublic struct {
|
|
|
|
k [ed25519.PublicKeySize]byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalText implements encoding.TextUnmarshaler.
|
|
|
|
func (k *NLPublic) UnmarshalText(b []byte) error {
|
|
|
|
return parseHex(k.k[:], mem.B(b), mem.S(nlPublicHexPrefix))
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalText implements encoding.TextMarshaler.
|
|
|
|
func (k NLPublic) MarshalText() ([]byte, error) {
|
|
|
|
return toHex(k.k[:], nlPublicHexPrefix), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verifier returns a ed25519.PublicKey that can be used to
|
|
|
|
// verify signatures.
|
|
|
|
func (k NLPublic) Verifier() ed25519.PublicKey {
|
|
|
|
return ed25519.PublicKey(k.k[:])
|
|
|
|
}
|