123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- 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
- }
|