-
Notifications
You must be signed in to change notification settings - Fork 991
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
9 changed files
with
459 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package nebula | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewPsk(t *testing.T) { | ||
t.Run("mode accepting", func(t *testing.T) { | ||
p, err := NewPsk(PskAccepting, nil) | ||
assert.NoError(t, err) | ||
assert.Equal(t, PskAccepting, p.mode) | ||
assert.Nil(t, p.keys[0]) | ||
assert.Nil(t, p.primary) | ||
|
||
p, err = NewPsk(PskAccepting, []string{"1234567"}) | ||
assert.Error(t, ErrKeyTooShort) | ||
|
||
p, err = NewPsk(PskAccepting, []string{"hi there friends"}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, PskAccepting, p.mode) | ||
assert.Nil(t, p.primary) | ||
assert.Len(t, p.keys, 2) | ||
assert.Nil(t, p.keys[1]) | ||
|
||
expectedCache := []byte{ | ||
0xb9, 0x8c, 0xdc, 0xac, 0x77, 0xf4, 0x8c, 0xf8, 0x1d, 0xe7, 0xe7, 0xb, 0x53, 0x25, 0xd3, 0x65, | ||
0xa3, 0x9f, 0x78, 0xb2, 0xc7, 0x2d, 0xa5, 0xd8, 0x84, 0x81, 0x7b, 0xb5, 0xdb, 0xe0, 0x9a, 0xef, | ||
} | ||
assert.Equal(t, expectedCache, p.keys[0]) | ||
}) | ||
|
||
t.Run("mode sending", func(t *testing.T) { | ||
p, err := NewPsk(PskSending, nil) | ||
assert.Error(t, ErrNotEnoughPskKeys, err) | ||
|
||
p, err = NewPsk(PskSending, []string{"1234567"}) | ||
assert.Error(t, ErrKeyTooShort) | ||
|
||
p, err = NewPsk(PskSending, []string{"hi there friends"}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, PskSending, p.mode) | ||
assert.Len(t, p.keys, 2) | ||
assert.Nil(t, p.keys[1]) | ||
|
||
expectedCache := []byte{ | ||
0xb9, 0x8c, 0xdc, 0xac, 0x77, 0xf4, 0x8c, 0xf8, 0x1d, 0xe7, 0xe7, 0xb, 0x53, 0x25, 0xd3, 0x65, | ||
0xa3, 0x9f, 0x78, 0xb2, 0xc7, 0x2d, 0xa5, 0xd8, 0x84, 0x81, 0x7b, 0xb5, 0xdb, 0xe0, 0x9a, 0xef, | ||
} | ||
assert.Equal(t, expectedCache, p.keys[0]) | ||
assert.Equal(t, p.keys[0], p.primary) | ||
}) | ||
|
||
t.Run("mode enforced", func(t *testing.T) { | ||
p, err := NewPsk(PskEnforced, nil) | ||
assert.Error(t, ErrNotEnoughPskKeys, err) | ||
|
||
p, err = NewPsk(PskEnforced, []string{"hi there friends"}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, PskEnforced, p.mode) | ||
assert.Len(t, p.keys, 1) | ||
|
||
expectedCache := []byte{ | ||
0xb9, 0x8c, 0xdc, 0xac, 0x77, 0xf4, 0x8c, 0xf8, 0x1d, 0xe7, 0xe7, 0xb, 0x53, 0x25, 0xd3, 0x65, | ||
0xa3, 0x9f, 0x78, 0xb2, 0xc7, 0x2d, 0xa5, 0xd8, 0x84, 0x81, 0x7b, 0xb5, 0xdb, 0xe0, 0x9a, 0xef, | ||
} | ||
assert.Equal(t, expectedCache, p.keys[0]) | ||
assert.Equal(t, p.keys[0], p.primary) | ||
}) | ||
} |