diff --git a/pkg/cryptstate/cryptstate.go b/pkg/cryptstate/cryptstate.go index 2b474cc..d5f7891 100644 --- a/pkg/cryptstate/cryptstate.go +++ b/pkg/cryptstate/cryptstate.go @@ -5,11 +5,9 @@ package cryptstate import ( - "crypto/aes" "crypto/rand" "errors" "io" - "mumbleapp.com/grumble/pkg/cryptstate/ocb2" "time" ) @@ -51,30 +49,39 @@ func SupportedModes() []string { } // createMode creates the CryptoMode with the given mode name. -func createMode(mode string) CryptoMode { +func createMode(mode string) (CryptoMode, error) { switch mode { case "OCB2-AES128": - return &ocb2Mode{} + return &ocb2Mode{}, nil + case "XSalsa20-Poly1305": + return &secretBoxMode{}, nil } - panic("cryptstate: no such CryptoMode") + return nil, errors.New("cryptstate: no such CryptoMode") } -func (cs *CryptState) GenerateKey() error { - cs.mode = createMode("OCB2-AES128") - cs.Key = make([]byte, cs.mode.KeySize()) - _, err := io.ReadFull(rand.Reader, cs.Key) +func (cs *CryptState) GenerateKey(mode string) error { + cm, err := createMode(mode) if err != nil { return err } - cs.mode.SetKey(cs.Key) - cs.EncryptIV = make([]byte, ocb2.NonceSize) + key := make([]byte, cm.KeySize()) + _, err = io.ReadFull(rand.Reader, key) + if err != nil { + return err + } + + cm.SetKey(key) + cs.mode = cm + cs.Key = key + + cs.EncryptIV = make([]byte, cm.NonceSize()) _, err = io.ReadFull(rand.Reader, cs.EncryptIV) if err != nil { return err } - cs.DecryptIV = make([]byte, ocb2.NonceSize) + cs.DecryptIV = make([]byte, cm.NonceSize()) _, err = io.ReadFull(rand.Reader, cs.DecryptIV) if err != nil { return err @@ -83,17 +90,19 @@ func (cs *CryptState) GenerateKey() error { return nil } -func (cs *CryptState) SetKey(key []byte, eiv []byte, div []byte) error { - cs.Key = key - cs.EncryptIV = eiv - cs.DecryptIV = div - - cipher, err := aes.NewCipher(cs.Key) +func (cs *CryptState) SetKey(mode string, key []byte, eiv []byte, div []byte) error { + cm, err := createMode(mode) if err != nil { return err } - cs.mode = &ocb2Mode{cipher: cipher} + cm.SetKey(key) + cs.mode = cm + cs.Key = key + + cs.EncryptIV = eiv + cs.DecryptIV = div + return nil } diff --git a/pkg/cryptstate/cryptstate_test.go b/pkg/cryptstate/cryptstate_test.go index 8be8eae..6c48d53 100644 --- a/pkg/cryptstate/cryptstate_test.go +++ b/pkg/cryptstate/cryptstate_test.go @@ -8,9 +8,10 @@ import ( "bytes" "crypto/aes" "testing" + "encoding/hex" ) -func TestEncrypt(t *testing.T) { +func TestOCB2AES128Encrypt(t *testing.T) { msg := [15]byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, } @@ -32,7 +33,7 @@ func TestEncrypt(t *testing.T) { cs := CryptState{} out := make([]byte, 19) - cs.SetKey(key[:], eiv[:], div[:]) + cs.SetKey("OCB2-AES128", key[:], eiv[:], div[:]) cs.Encrypt(out, msg[:]) if !bytes.Equal(out[:], expected[:]) { @@ -44,7 +45,7 @@ func TestEncrypt(t *testing.T) { } } -func TestDecrypt(t *testing.T) { +func TestOCB2AES128Decrypt(t *testing.T) { key := [aes.BlockSize]byte{ 0x96, 0x8b, 0x1b, 0x0c, 0x53, 0x1e, 0x1f, 0x80, 0xa6, 0x1d, 0xcb, 0x27, 0x94, 0x09, 0x6f, 0x32, } @@ -66,7 +67,7 @@ func TestDecrypt(t *testing.T) { cs := CryptState{} out := make([]byte, 15) - cs.SetKey(key[:], div[:], eiv[:]) + cs.SetKey("OCB2-AES128", key[:], div[:], eiv[:]) cs.Decrypt(out, crypted[:]) if !bytes.Equal(out, expected[:]) { @@ -77,3 +78,79 @@ func TestDecrypt(t *testing.T) { t.Errorf("Mismatch in DIV") } } + +// Test that our wrapped NaCl secretbox cipher +// works. The test data for this test was lifted +// from the secretbox_test.go file. +func TestXSalsa20Poly1305Encrypt(t *testing.T) { + cs := CryptState{} + + var key[32]byte + var eiv[24]byte + var div[24]byte + var message [64]byte + + for i := range key[:] { + key[i] = 1 + } + + // Since we pre-increment our EIV, + // this look a bit off compared to + // the secretbox_test.go test case. + for i := range eiv[:] { + eiv[i] = 2 + div[i] = 2 + } + eiv[0] = 1 + div[0] = 1 + + for i := range message[:] { + message[i] = 3 + } + + cs.SetKey("XSalsa20-Poly1305", key[:], div[:], eiv[:]) + dst := make([]byte, len(message)+cs.Overhead()) + cs.Encrypt(dst, message[:]) + + expected, _ := hex.DecodeString("8442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad") + if !bytes.Equal(dst[1:], expected) { + t.Fatalf("mismatch! got\n%x\n, expected\n%x", dst, expected) + } +} + +// Test that we can reverse the result of the Encrypt test. +func TestXSalsa20Poly1305Decrypt(t *testing.T) { + cs := CryptState{} + + var key[32]byte + var eiv[24]byte + var div[24]byte + var expected [64]byte + + for i := range key[:] { + key[i] = 1 + } + + // Since we pre-increment our EIV, + // this look a bit off compared to + // the secretbox_test.go test case. + for i := range eiv[:] { + eiv[i] = 2 + div[i] = 2 + } + eiv[0] = 1 + div[0] = 1 + + for i := range expected[:] { + expected[i] = 3 + } + + message, _ := hex.DecodeString("028442bc313f4626f1359e3b50122b6ce6fe66ddfe7d39d14e637eb4fd5b45beadab55198df6ab5368439792a23c87db70acb6156dc5ef957ac04f6276cf6093b84be77ff0849cc33e34b7254d5a8f65ad") + cs.SetKey("XSalsa20-Poly1305", key[:], div[:], eiv[:]) + dst := make([]byte, len(message)-cs.Overhead()) + cs.Decrypt(dst, message[:]) + + if !bytes.Equal(dst, expected[:]) { + t.Fatalf("mismatch! got\n%x\n, expected\n%x", dst, expected) + } +} \ No newline at end of file diff --git a/pkg/cryptstate/mode_secretbox.go b/pkg/cryptstate/mode_secretbox.go new file mode 100644 index 0000000..12656ec --- /dev/null +++ b/pkg/cryptstate/mode_secretbox.go @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package cryptstate + +import ( + "unsafe" + "code.google.com/p/go.crypto/nacl/secretbox" +) + +// secretBoxMode implements the XSalsa20-Poly1305 CryptoMode +type secretBoxMode struct { + key [32]byte +} + +// NonceSize returns the nonce size to be used with XSalsa20-Poly1305. +func (sb *secretBoxMode) NonceSize() int { + return 24 +} + +// KeySize returns the key size to be used with XSalsa20-Poly1305. +func (sb *secretBoxMode) KeySize() int { + return 32 +} + +// Overhead returns the overhead that a ciphertext has over a plaintext. +// In the case of XSalsa20-Poly1305 the overhead is the authentication tag. +func (sb *secretBoxMode) Overhead() int { + return secretbox.Overhead +} + +// SetKey sets a new key. The key must have a length equal to KeySize(). +func (sb *secretBoxMode) SetKey(key []byte) { + if len(key) != sb.KeySize() { + panic("cryptstate: invalid key length") + } + copy(sb.key[:], key) +} + +// Encrypt encrypts a message using XSalsa20-Poly1305 and outputs it to dst. +func (sb *secretBoxMode) Encrypt(dst []byte, src []byte, nonce []byte) { + if len(dst) <= sb.Overhead() { + panic("cryptstate: bad dst") + } + + if len(nonce) != 24 { + panic("cryptstate: bad nonce length") + } + + noncePtr := (*[24]byte)(unsafe.Pointer(&nonce[0])) + secretbox.Seal(dst[0:0], src, noncePtr, &sb.key) +} + +// Decrypt decrypts a message using XSalsa20-Poly1305 and outputs it to dst. +// Returns false if decryption failed (authentication tag mismatch). +func (sb *secretBoxMode) Decrypt(dst []byte, src []byte, nonce []byte) bool { + if len(src) <= sb.Overhead() { + panic("cryptstate: bad src") + } + + if len(nonce) != 24 { + panic("cryptstate: bad nonce length") + } + + noncePtr := (*[24]byte)(unsafe.Pointer(&nonce[0])) + _, ok := secretbox.Open(dst[0:0], src, noncePtr, &sb.key) + return ok +} diff --git a/server.go b/server.go index 2a36f68..8213eac 100644 --- a/server.go +++ b/server.go @@ -504,7 +504,7 @@ func (server *Server) handleAuthenticate(client *Client, msg *Message) { } // Setup the cryptstate for the client. - err = client.crypt.GenerateKey() + err = client.crypt.GenerateKey(client.CryptoMode) if err != nil { client.Panicf("%v", err) return