mirror of
https://github.com/slackhq/nebula.git
synced 2025-01-27 10:19:04 +00:00
183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
package nebula
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/slackhq/nebula/config"
|
|
"github.com/slackhq/nebula/iputil"
|
|
"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")
|
|
|
|
// The minimum length that we accept for a user defined psk, the choice is arbitrary
|
|
const MinPskLength = 8
|
|
|
|
type PskMode int
|
|
|
|
func (p PskMode) String() string {
|
|
switch p {
|
|
case PskNone:
|
|
return "none"
|
|
case PskTransitionalAccepting:
|
|
return "transitional-accepting"
|
|
case PskTransitionalSending:
|
|
return "transitional-sending"
|
|
case PskEnforced:
|
|
return "enforced"
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
func NewPskMode(m string) (PskMode, error) {
|
|
switch m {
|
|
case "none":
|
|
return PskNone, nil
|
|
case "transitional-accepting":
|
|
return PskTransitionalAccepting, nil
|
|
case "transitional-sending":
|
|
return PskTransitionalSending, nil
|
|
case "enforced":
|
|
return PskEnforced, nil
|
|
}
|
|
return PskNone, ErrNotAPskMode
|
|
}
|
|
|
|
const (
|
|
PskNone PskMode = 0
|
|
PskTransitionalAccepting PskMode = 1
|
|
PskTransitionalSending PskMode = 2
|
|
PskEnforced PskMode = 3
|
|
)
|
|
|
|
type Psk struct {
|
|
// pskMode sets how psk works, ignored, allowed for incoming, or enforced for all
|
|
mode PskMode
|
|
|
|
// Cache holds all pre-computed psk hkdfs
|
|
// Handshakes iterate this directly
|
|
Cache [][]byte
|
|
|
|
// The key has already been extracted and is ready to be expanded for use
|
|
// MakeFor does the final expand step mixing in the intended recipients vpn ip
|
|
key []byte
|
|
}
|
|
|
|
// NewPskFromConfig is a helper for initial boot and config reloading.
|
|
func NewPskFromConfig(c *config.C, myVpnIp iputil.VpnIp) (*Psk, error) {
|
|
sMode := c.GetString("handshakes.psk.mode", "none")
|
|
mode, err := NewPskMode(sMode)
|
|
if err != nil {
|
|
return nil, NewContextualError("Could not parse handshakes.psk.mode", m{"mode": mode}, err)
|
|
}
|
|
|
|
return NewPsk(
|
|
mode,
|
|
c.GetStringSlice("handshakes.psk.keys", nil),
|
|
myVpnIp,
|
|
)
|
|
}
|
|
|
|
// NewPsk creates a new Psk object and handles the caching of all accepted keys and preparation of the primary key
|
|
func NewPsk(mode PskMode, keys []string, myVpnIp iputil.VpnIp) (*Psk, error) {
|
|
psk := &Psk{
|
|
mode: mode,
|
|
}
|
|
|
|
err := psk.preparePrimaryKey(keys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = psk.cachePsks(myVpnIp, keys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return psk, nil
|
|
}
|
|
|
|
// MakeFor if we are in enforced mode, the final hkdf expand stage is done on the pre extracted primary key,
|
|
// mixing in the intended recipients vpn ip and the result is returned.
|
|
// If we are transitional or not using psks, an empty byte slice is returned
|
|
func (p *Psk) MakeFor(vpnIp iputil.VpnIp) ([]byte, error) {
|
|
if p.mode == PskNone || p.mode == PskTransitionalAccepting {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
hmacKey := make([]byte, sha256.Size)
|
|
_, err := io.ReadFull(hkdf.Expand(sha256.New, p.key, vpnIp.ToIP()), hmacKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hmacKey, nil
|
|
}
|
|
|
|
// cachePsks generates all psks we accept and caches them to speed up handshaking
|
|
func (p *Psk) cachePsks(myVpnIp iputil.VpnIp, keys []string) error {
|
|
// If PskNone is set then we are using the nil byte array for a psk, we can return
|
|
if p.mode == PskNone {
|
|
p.Cache = [][]byte{nil}
|
|
return nil
|
|
}
|
|
|
|
if len(keys) < 1 {
|
|
return ErrNotEnoughPskKeys
|
|
}
|
|
|
|
p.Cache = [][]byte{}
|
|
|
|
if p.mode == PskTransitionalAccepting || p.mode == PskTransitionalSending {
|
|
// We are transitional, we accept empty psks
|
|
p.Cache = append(p.Cache, nil)
|
|
}
|
|
|
|
// We are either PskAuto or PskTransitional, build all possibilities
|
|
for i, rk := range keys {
|
|
k, err := sha256KdfFromString(rk, myVpnIp)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate key for position %v: %w", i, err)
|
|
}
|
|
|
|
p.Cache = append(p.Cache, k)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// preparePrimaryKey if we are in enforced mode, will do an hkdf extract on the first key to benefit
|
|
// outgoing handshake performance, MakeFor does the final expand step
|
|
func (p *Psk) preparePrimaryKey(keys []string) error {
|
|
if p.mode == PskNone || p.mode == PskTransitionalAccepting {
|
|
// If we aren't enforcing then there is nothing to prepare
|
|
return nil
|
|
}
|
|
|
|
if len(keys) < 1 {
|
|
return ErrNotEnoughPskKeys
|
|
}
|
|
|
|
p.key = hkdf.Extract(sha256.New, []byte(keys[0]), nil)
|
|
return nil
|
|
}
|
|
|
|
// sha256KdfFromString generates a full hkdf
|
|
func sha256KdfFromString(secret string, vpnIp iputil.VpnIp) ([]byte, error) {
|
|
if len(secret) < MinPskLength {
|
|
return nil, ErrKeyTooShort
|
|
}
|
|
|
|
hmacKey := make([]byte, sha256.Size)
|
|
_, err := io.ReadFull(hkdf.New(sha256.New, []byte(secret), nil, vpnIp.ToIP()), hmacKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return hmacKey, nil
|
|
}
|