Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,17 @@ type Config struct {
// AES-192, or AES-256.
SecretKey []byte

// A unique key per cluster that is used enhance encryption keys to provide
// basic authentication. When this is set, the PKI version of the protocol
// is enabled.
AccessKey []byte

// Private key to use for public key encryption along with the AccessKey.
// If one is not specified, an ephemeral key will be used. This is exposed
// to allow higher layers to generate and note keys to allow them to reject
// unknown keys (and thus nodes) from cluster access.
PrivateKey PrivateKey

// The keyring holds all of the encryption keys used internally. It is
// automatically initialized using the SecretKey and SecretKeys values.
Keyring *Keyring
Expand Down Expand Up @@ -309,5 +320,5 @@ func DefaultLocalConfig() *Config {

// Returns whether or not encryption is enabled
func (c *Config) EncryptionEnabled() bool {
return c.Keyring != nil && len(c.Keyring.GetKeys()) > 0
return (c.Keyring != nil && len(c.Keyring.GetKeys()) > 0) || c.ProtocolVersion == ProtocolPKIVersion1
}
260 changes: 260 additions & 0 deletions encryption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package memberlist

import (
"bytes"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"

"golang.org/x/crypto/curve25519"
Comment thread
abennett marked this conversation as resolved.
)

// basic implemenation, cribbed from wireguard-go

// Generate a new key to use for encryption. This is a convenience wrapper
// to provide the key in the proper format for use in Memberlist config and
// join Addresses.
func GenerateKey() (private string, public string, err error) {
pk, err := NewPrivateKey()
if err != nil {
return "", "", err
}

return pk.HexString(), pk.Public().HexString(), nil
}

const KeySize = 32

type ParseError struct {
Reason string
Input string
}

func (p *ParseError) Error() string {
return p.Reason
}

// Key is curve25519 key.
type (
Key []byte
KeyArray [32]byte
)

// newPresharedKey generates a new random key.
func newPresharedKey() (Key, error) {
var k [KeySize]byte
_, err := rand.Read(k[:])
if err != nil {
return nil, err
}
return k[:], nil
}

func (k Key) MapKey() KeyArray {
var ka KeyArray
copy(ka[:], k)
return ka
}

func ReadRandom(size int) ([]byte, error) {
data := make([]byte, size)

_, err := io.ReadFull(rand.Reader, data)
if err != nil {
return nil, err
}

return data, nil
}

func ParseHexKey(s string) (Key, error) {
b, err := hex.DecodeString(s)
if err != nil {
return Key{}, &ParseError{"invalid hex key: " + err.Error(), s}
}
if len(b) != KeySize {
return Key{}, &ParseError{fmt.Sprintf("invalid hex key length: %d", len(b)), s}
}

var key Key
copy(key[:], b)
return key, nil
}

func ParsePrivateHexKey(v string) (PrivateKey, error) {
k, err := ParseHexKey(v)
if err != nil {
return PrivateKey{}, err
}
pk := PrivateKey(k)
if pk.IsZero() {
// Do not clamp a zero key, pass the zero through
// (much like NaN propagation) so that IsZero reports
// a useful result.
return pk, nil
}
pk.clamp()
return pk, nil
}

func (k Key) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) }
func (k Key) String() string { return "pub:" + k.Base64()[:8] }
func (k Key) HexString() string { return hex.EncodeToString(k[:]) }
func (k Key) Equal(k2 Key) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }

func (k *Key) ShortString() string {
if k.IsZero() {
return "[empty]"
}
long := k.String()
if len(long) < 10 {
return "invalid"
}
return "[" + long[0:4] + "…" + long[len(long)-5:len(long)-1] + "]"
}

func (k Key) IsZero() bool {
if k == nil {
return true
}
var zeros Key
return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
}

func (k Key) Bytes() []byte {
if k.IsZero() {
return nil
}

return k
}

func (k Key) MarshalJSON() ([]byte, error) {
if k == nil {
return []byte("null"), nil
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, `"%x"`, k[:])
return buf.Bytes(), nil
}

func (k Key) UnmarshalJSON(b []byte) error {
if k == nil {
return errors.New("wgcfg.Key: UnmarshalJSON on nil pointer")
}
if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' {
return errors.New("wgcfg.Key: UnmarshalJSON not given a string")
}
b = b[1 : len(b)-1]
key, err := ParseHexKey(string(b))
if err != nil {
return fmt.Errorf("wgcfg.Key: UnmarshalJSON: %v", err)
}
copy(k[:], key[:])
return nil
}

func (a Key) LessThan(b Key) bool {
for i := range a {
if a[i] < b[i] {
return true
} else if a[i] > b[i] {
return false
}
}
return false
}

// PrivateKey is curve25519 key.
type PrivateKey []byte

// NewPrivateKey generates a new curve25519 secret key.
// It conforms to the format described on https://cr.yp.to/ecdh.html.
func NewPrivateKey() (PrivateKey, error) {
k, err := newPresharedKey()
if err != nil {
return PrivateKey{}, err
}
k[0] &= 248
k[31] = (k[31] & 127) | 64
return (PrivateKey)(k), nil
}

func ParsePrivateKey(b64 string) (*PrivateKey, error) {
k, err := parseKeyBase64(base64.StdEncoding, b64)
return (*PrivateKey)(k), err
}

func (k PrivateKey) String() string { return base64.StdEncoding.EncodeToString(k[:]) }
func (k PrivateKey) HexString() string { return hex.EncodeToString(k[:]) }
func (k PrivateKey) Equal(k2 PrivateKey) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }

func (k *PrivateKey) IsZero() bool {
pk := Key(*k)
return pk.IsZero()
}

func (k PrivateKey) clamp() {
k[0] &= 248
k[31] = (k[31] & 127) | 64
}

// Public computes the public key matching this curve25519 secret key.
func (k PrivateKey) Public() Key {
pk := Key(k)
if pk.IsZero() {
panic("Tried to generate emptyPrivateKey.Public()")
}
var p, tk [KeySize]byte

copy(tk[:], k)
curve25519.ScalarBaseMult(&p, &tk)
return p[:]
}

func (k PrivateKey) MarshalText() ([]byte, error) {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, `privkey:%x`, k[:])
return buf.Bytes(), nil
}

func (k PrivateKey) UnmarshalText(b []byte) error {
s := string(b)
if !strings.HasPrefix(s, `privkey:`) {
return errors.New("wgcfg.PrivateKey: UnmarshalText not given a private-key string")
}
s = strings.TrimPrefix(s, `privkey:`)
key, err := ParseHexKey(s)
if err != nil {
return fmt.Errorf("wgcfg.PrivateKey: UnmarshalText: %v", err)
}
copy(k[:], key[:])
return nil
}

func (k PrivateKey) SharedSecret(pub Key) (ss [KeySize]byte) {
var apk, ask [KeySize]byte

copy(apk[:], pub)
copy(ask[:], k)
curve25519.ScalarMult(&ss, &ask, &apk)
return ss
}

func parseKeyBase64(enc *base64.Encoding, s string) (*Key, error) {
k, err := enc.DecodeString(s)
if err != nil {
return nil, &ParseError{"Invalid key: " + err.Error(), s}
}
if len(k) != KeySize {
return nil, &ParseError{"Keys must decode to exactly 32 bytes", s}
}
var key Key
copy(key[:], k)
return &key, nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
)
10 changes: 5 additions & 5 deletions keyring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ func TestKeyRing_MultiKeyEncryptDecrypt(t *testing.T) {

// First encrypt using the primary key and make sure we can decrypt
var buf bytes.Buffer
err = encryptPayload(1, TestKeys[0], plaintext, extra, &buf)
err = encryptPayload(1, TestKeys[0], plaintext, extra, nil, &buf)
if err != nil {
t.Fatalf("err: %v", err)
}

msg, err := decryptPayload(keyring.GetKeys(), buf.Bytes(), extra)
msg, err := decryptPayload(nil, keyring.GetKeys(), buf.Bytes(), extra)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand All @@ -128,12 +128,12 @@ func TestKeyRing_MultiKeyEncryptDecrypt(t *testing.T) {

// Now encrypt with a secondary key and try decrypting again.
buf.Reset()
err = encryptPayload(1, TestKeys[2], plaintext, extra, &buf)
err = encryptPayload(1, TestKeys[2], plaintext, extra, nil, &buf)
if err != nil {
t.Fatalf("err: %v", err)
}

msg, err = decryptPayload(keyring.GetKeys(), buf.Bytes(), extra)
msg, err = decryptPayload(nil, keyring.GetKeys(), buf.Bytes(), extra)
if err != nil {
t.Fatalf("err: %v", err)
}
Expand All @@ -147,7 +147,7 @@ func TestKeyRing_MultiKeyEncryptDecrypt(t *testing.T) {
t.Fatalf("err: %s", err)
}

msg, err = decryptPayload(keyring.GetKeys(), buf.Bytes(), extra)
msg, err = decryptPayload(nil, keyring.GetKeys(), buf.Bytes(), extra)
if err == nil {
t.Fatalf("Expected no keys to decrypt message")
}
Expand Down
Loading