Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add public key encryption #216

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
7 changes: 6 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ 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

// 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 +314,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"
abennett marked this conversation as resolved.
Show resolved Hide 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