Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into fb/storage-abstrac…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
daeMOn63 committed Feb 14, 2020
2 parents 2dfa90a + f0b0db4 commit 4a6a13f
Show file tree
Hide file tree
Showing 10 changed files with 443 additions and 54 deletions.
19 changes: 19 additions & 0 deletions client.go
Expand Up @@ -103,6 +103,8 @@ type Client interface {
removeTopic(topicHash []byte) error
// resetTopics will remove all previously set topics from the client.
resetTopics() error
// setC2Key instructs the device to replace the current C2 public key with the newly transmitted one.
setC2Key(newC2PubKey e4crypto.Curve25519PublicKey) error
}

// client implements Client interface
Expand Down Expand Up @@ -578,6 +580,23 @@ func (c *client) setIDKey(key []byte) error {
return c.save()
}

func (c *client) setC2Key(newC2PubKey e4crypto.Curve25519PublicKey) error {
pkStore, ok := c.Key.(keys.PubKeyStore)
if !ok {
return ErrUnsupportedOperation
}

if err := e4crypto.ValidateCurve25519PubKey(newC2PubKey); err != nil {
return fmt.Errorf("invalid c2 public key: %v", err)
}

if err := pkStore.SetC2PubKey(newC2PubKey); err != nil {
return err
}

return c.save()
}

// TopicForID generate the receiving topic that a client should subscribe to in order to receive commands
func TopicForID(id []byte) string {
return idTopicPrefix + hex.EncodeToString(id)
Expand Down
74 changes: 71 additions & 3 deletions client_test.go
Expand Up @@ -609,6 +609,51 @@ func TestClientSetIDKey(t *testing.T) {

}

func TestClientSetC2Key(t *testing.T) {
c2PubKey, err := curve25519.X25519(e4crypto.RandomKey(), curve25519.Basepoint)
if err != nil {
t.Fatalf("failed to generate public curve25519 key: %v", err)
}

c, err := NewClient(&SymIDAndKey{
ID: e4crypto.HashIDAlias("client1"),
Key: e4crypto.RandomKey(),
}, NewInMemoryStore(nil))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}

if err := c.setC2Key(c2PubKey); err != ErrUnsupportedOperation {
t.Fatalf("Got error %v, wanted %v", err, ErrUnsupportedOperation)
}

_, edSk, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("Failed to generate ed25519 private key: %v", err)
}

c, err = NewClient(&PubIDAndKey{
ID: e4crypto.HashIDAlias("client1"),
Key: edSk,
C2PubKey: c2PubKey,
}, NewInMemoryStore(nil))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}

newC2PubKey, err := curve25519.X25519(e4crypto.RandomKey(), curve25519.Basepoint)
if err != nil {
t.Fatalf("failed to generate public curve25519 key: %v", err)
}
if err := c.setC2Key(newC2PubKey[1:]); err == nil {
t.Fatalf("Got no error while setting an invald c2 public key")
}

if err := c.setC2Key(newC2PubKey); err != nil {
t.Fatalf("Failed to set c2 public key: %v", err)
}
}

func TestCommandsSymClient(t *testing.T) {
clientID := e4crypto.HashIDAlias("client1")
clientKey := e4crypto.RandomKey()
Expand Down Expand Up @@ -807,7 +852,7 @@ func TestCommandsSymClient(t *testing.T) {
t.Fatalf("Failed to protect command: %v", err)
}
if _, err := c.Unprotect(badProtectedSetPubKeyCmd, receivingTopic); err == nil {
t.Fatal("Expected an error with a bad setIDKey Command length")
t.Fatal("Expected an error with a bad setPubKey Command length")
}

_, err = c.Unprotect(protectedSetPubKeyCmd, receivingTopic)
Expand All @@ -829,7 +874,7 @@ func TestCommandsSymClient(t *testing.T) {
t.Fatalf("Failed to protect command: %v", err)
}
if _, err := c.Unprotect(badProtectedRemovePubKeyCmd, receivingTopic); err == nil {
t.Fatal("Expected an error with a bad setIDKey Command length")
t.Fatal("Expected an error with a bad removePubKey Command length")
}

_, err = c.Unprotect(protectedRemovePubKeyCmd, receivingTopic)
Expand All @@ -850,14 +895,37 @@ func TestCommandsSymClient(t *testing.T) {
t.Fatalf("Failed to protect command: %v", err)
}
if _, err := c.Unprotect(badProtectedResetPubKeyCmd, receivingTopic); err == nil {
t.Fatal("Expected an error with a bad setIDKey Command length")
t.Fatal("Expected an error with a bad resetPubKey Command length")
}

_, err = c.Unprotect(protectedResetPubKeyCmd, receivingTopic)
if err != ErrUnsupportedOperation {
t.Fatalf("Invalid error when unprotecting command: got %v, wanted %v", err, ErrUnsupportedOperation)
}

setC2KeyCmd := []byte{SetC2Key}
badProtectedSetC2KeyCmd, err := e4crypto.ProtectSymKey(append(setC2KeyCmd, 0x01), newClientKey)
if err != nil {
t.Fatalf("Failed to protect command: %v", err)
}
if _, err := c.Unprotect(badProtectedSetC2KeyCmd, receivingTopic); err == nil {
t.Fatal("Expected an error with a bad setC2Key Command length")
}

pubkey, err := curve25519.X25519(e4crypto.RandomKey(), curve25519.Basepoint)
if err != nil {
t.Fatalf("failed to generate pubkey: %v", err)
}
protectedSetC2KeyCmd, err := e4crypto.ProtectSymKey(append(setC2KeyCmd, pubkey...), newClientKey)
if err != nil {
t.Fatalf("Failed to protect command: %v", err)
}

_, err = c.Unprotect(protectedSetC2KeyCmd, receivingTopic)
if err != ErrUnsupportedOperation {
t.Fatalf("Invalid error when unprotecting command: got %v, wanted %v", err, ErrUnsupportedOperation)
}

// Unknown command
unknownCmd := []byte{0xFF}
protectedUnknownCmd, err := e4crypto.ProtectSymKey(unknownCmd, newClientKey)
Expand Down
18 changes: 18 additions & 0 deletions commands.go
Expand Up @@ -46,6 +46,8 @@ const (
// SetPubKey allows to set a public key on the client.
// It takes a public key, followed by an ID as arguments.
SetPubKey
// SetC2PubKey replaces the current C2 public key with the newly transmitted one.
SetC2Key

// UnknownCommand must stay the last element. It's used to
// know if a Command is out of range
Expand Down Expand Up @@ -104,6 +106,11 @@ func processCommand(client Client, payload []byte) error {
return errors.New("invalid SetPubKey length")
}
return client.setPubKey(blob[:ed25519.PublicKeySize], blob[ed25519.PublicKeySize:])
case SetC2Key:
if len(blob) != e4crypto.Curve25519PubKeyLen {
return errors.New("invalid SetC2Key length")
}
return client.setC2Key(blob[:e4crypto.Curve25519PubKeyLen])

default:
return ErrInvalidCommand
Expand Down Expand Up @@ -187,3 +194,14 @@ func CmdSetPubKey(pubKey e4crypto.Ed25519PublicKey, name string) ([]byte, error)

return cmd, nil
}

// CmdSetC2Key creates a command to replace the c2 public key by the given one.
func CmdSetC2Key(c2PubKey e4crypto.Curve25519PublicKey) ([]byte, error) {
if err := e4crypto.ValidateCurve25519PubKey(c2PubKey); err != nil {
return nil, fmt.Errorf("invalid c2 public key: %v", err)
}

cmd := append([]byte{SetC2Key}, c2PubKey...)

return cmd, nil
}
41 changes: 37 additions & 4 deletions commands_test.go
Expand Up @@ -19,6 +19,7 @@ import (
"crypto/rand"
"testing"

"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"

e4crypto "github.com/teserakt-io/e4go/crypto"
Expand All @@ -42,6 +43,13 @@ var invalidPubKeys = []ed25519.PublicKey{
make([]byte, ed25519.PublicKeySize+1),
}

var invalidCurve25519PubKeys = []e4crypto.Curve25519PublicKey{
nil,
[]byte{},
make([]byte, e4crypto.Curve25519PubKeyLen-1),
make([]byte, e4crypto.Curve25519PubKeyLen+1),
}

func TestCmdRemoveTopic(t *testing.T) {
t.Run("invalid names produce errors", func(t *testing.T) {
for _, name := range invalidNames {
Expand Down Expand Up @@ -251,10 +259,35 @@ func TestCmdSetPubKey(t *testing.T) {
})
}

func TestToByte(t *testing.T) {
t.Run("ToByte() returns 255 for out of range commands", func(t *testing.T) {
if UnknownCommand != 255 {
t.Fatalf("expected unknown command byte to be %d, got %d", 255, UnknownCommand)
func TestCmdSetC2Key(t *testing.T) {
t.Run("invalid keys produce errors", func(t *testing.T) {
for _, k := range invalidCurve25519PubKeys {
_, err := CmdSetC2Key(k)
if err == nil {
t.Fatalf("got no error with key %v", k)
}
}
})

t.Run("expected command is created", func(t *testing.T) {
privKey := e4crypto.RandomKey()
expectedKey, err := curve25519.X25519(privKey, curve25519.Basepoint)
if err != nil {
t.Fatalf("failed to generate public key: %v", err)
}

cmd, err := CmdSetC2Key(expectedKey)
if err != nil {
t.Fatalf("failed to create command: %v", err)
}

if got, want := len(cmd), 1+e4crypto.Curve25519PubKeyLen; got != want {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{SetC2Key}, expectedKey...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
})
}
15 changes: 15 additions & 0 deletions keys/json.go
Expand Up @@ -72,5 +72,20 @@ func FromRawJSON(raw json.RawMessage) (KeyMaterial, error) {
return nil, err
}

if err := clientKey.validate(); err != nil {
return nil, fmt.Errorf("invalid clientKey: %v", err)
}

// Recompute shared key after unmarshalling the keymaterial
// as this field is not exported
pubKey, ok := clientKey.(*pubKeyMaterial)
if !ok {
return clientKey, nil
}

if err := pubKey.updateSharedKey(); err != nil {
return nil, err
}

return clientKey, nil
}

0 comments on commit 4a6a13f

Please sign in to comment.