mirror of
https://github.com/slackhq/nebula.git
synced 2025-01-11 03:48:12 +00:00
150 lines
3.1 KiB
Go
150 lines
3.1 KiB
Go
package nebula
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/slackhq/nebula/config"
|
|
"github.com/slackhq/nebula/util"
|
|
"golang.org/x/crypto/hkdf"
|
|
)
|
|
|
|
var ErrNotAPskMode = errors.New("not a psk mode")
|
|
var ErrKeyTooShort = errors.New("key is too short")
|
|
var ErrNotEnoughPskKeys = errors.New("at least 1 key is required")
|
|
|
|
// MinPskLength is the minimum bytes that we accept for a user defined psk, the choice is arbitrary
|
|
const MinPskLength = 8
|
|
|
|
type PskMode int
|
|
|
|
const (
|
|
PskAccepting PskMode = 0
|
|
PskSending PskMode = 1
|
|
PskEnforced PskMode = 2
|
|
)
|
|
|
|
func NewPskMode(m string) (PskMode, error) {
|
|
switch m {
|
|
case "accepting":
|
|
return PskAccepting, nil
|
|
case "sending":
|
|
return PskSending, nil
|
|
case "enforced":
|
|
return PskEnforced, nil
|
|
}
|
|
return PskAccepting, ErrNotAPskMode
|
|
}
|
|
|
|
func (p PskMode) String() string {
|
|
switch p {
|
|
case PskAccepting:
|
|
return "accepting"
|
|
case PskSending:
|
|
return "sending"
|
|
case PskEnforced:
|
|
return "enforced"
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
func (p PskMode) IsValid() bool {
|
|
switch p {
|
|
case PskAccepting, PskSending, PskEnforced:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
type Psk struct {
|
|
// pskMode sets how psk works, ignored, allowed for incoming, or enforced for all
|
|
mode PskMode
|
|
|
|
// primary is the key to use when sending, it may be nil
|
|
primary []byte
|
|
|
|
// keys holds all pre-computed psk hkdfs
|
|
// Handshakes iterate this directly
|
|
keys [][]byte
|
|
}
|
|
|
|
// NewPskFromConfig is a helper for initial boot and config reloading.
|
|
func NewPskFromConfig(c *config.C) (*Psk, error) {
|
|
sMode := c.GetString("psk.mode", "accepting")
|
|
mode, err := NewPskMode(sMode)
|
|
if err != nil {
|
|
return nil, util.NewContextualError("Could not parse psk.mode", m{"mode": mode}, err)
|
|
}
|
|
|
|
return NewPsk(
|
|
mode,
|
|
c.GetStringSlice("psk.keys", nil),
|
|
)
|
|
}
|
|
|
|
// NewPsk creates a new Psk object and handles the caching of all accepted keys
|
|
func NewPsk(mode PskMode, keys []string) (*Psk, error) {
|
|
if !mode.IsValid() {
|
|
return nil, ErrNotAPskMode
|
|
}
|
|
|
|
psk := &Psk{
|
|
mode: mode,
|
|
}
|
|
|
|
err := psk.cachePsks(keys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return psk, nil
|
|
}
|
|
|
|
// cachePsks generates all psks we accept and caches them to speed up handshaking
|
|
func (p *Psk) cachePsks(keys []string) error {
|
|
if p.mode != PskAccepting && len(keys) < 1 {
|
|
return ErrNotEnoughPskKeys
|
|
}
|
|
|
|
p.keys = [][]byte{}
|
|
|
|
for i, rk := range keys {
|
|
k, err := sha256KdfFromString(rk)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate key for position %v: %w", i, err)
|
|
}
|
|
|
|
p.keys = append(p.keys, k)
|
|
}
|
|
|
|
if p.mode != PskAccepting {
|
|
// We are either sending or enforcing, the primary key must the first slot
|
|
p.primary = p.keys[0]
|
|
}
|
|
|
|
if p.mode != PskEnforced {
|
|
// If we are not enforcing psk use then a nil psk is allowed
|
|
p.keys = append(p.keys, nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// sha256KdfFromString generates a useful key to use from a provided secret
|
|
func sha256KdfFromString(secret string) ([]byte, error) {
|
|
if len(secret) < MinPskLength {
|
|
return nil, ErrKeyTooShort
|
|
}
|
|
|
|
hmacKey := make([]byte, sha256.Size)
|
|
_, err := io.ReadFull(hkdf.New(sha256.New, []byte(secret), nil, nil), hmacKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hmacKey, nil
|
|
}
|