Skip to content

Commit

Permalink
Merge pull request #29 from teserakt-io/fb/mobile-bindings
Browse files Browse the repository at this point in the history
Added Android bindings support
  • Loading branch information
diagprov authored Jan 17, 2020
2 parents 61c2f17 + 931a6b5 commit d4e2f71
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 73 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* [Transmitting a message](#transmitting-a-message)
* [Handling errors](#handling-errors)
* [Key generation](#key-generation)
* [Bindings](#bindings)
* [Android](#android)
- [Contributing](#contributing)
- [Security](#security)
- [Support](#support)
Expand Down Expand Up @@ -182,6 +184,42 @@ You can [download](https://github.com/teserakt-io/e4go/releases) the binary for

Our key generator relies on Go's `crypto/rand` package, which guarantees cryptographically secure randomness across various platforms.

### Bindings

#### Android

Latest bindings for Android can be downloaded from the [release page](https://github.com/teserakt-io/e4go/releases).
On an environment having an Android SDK and NDK available, an Android AAR package can be generated invoking the following script:

```bash
./scripts/android_bindings.sh
```

This will generate:

- `dist/bindings/android/e4.aar`: the Android package, containing compiled Java class and native libraries for most common architectures
- `dist/bindings/android/e4-sources.jar`: the Java source files

After importing the AAR in your project, E4 client can be created and invoked
in a similar way than the Go version, for example using Kotlin:

```kotlin
import io.teserakt.e4.*
import io.teserakt.crypto.*

val cfg = SymNameAndPassword()
cfg.name = "deviceXYZ"
cfg.password = "secretForDeviceXYZ"

val client = E4.newClient(cfg, cacheDir.path + "deviceXYZ.json")

// From here, messages can be protected / unprotected :
val topic = "/deviceXYZ/data";
val protectedMessage = client.protectMessage("Hello".toByteArray(Charsets.UTF_8), topic)
val unprotectedMessage = client.unprotect(protectedMessage, topic)
```


## Contributing

Before contributing, please read our [CONTRIBUTING](./CONTRIBUTING.md) guide.
Expand Down
6 changes: 3 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ type SymNameAndPassword struct {
// from an ID, an ed25519 private key, and a curve25519 public key.
type PubIDAndKey struct {
ID []byte
Key ed25519.PrivateKey
Key e4crypto.Ed25519PrivateKey
C2PubKey e4crypto.Curve25519PublicKey
}

Expand Down Expand Up @@ -232,13 +232,13 @@ func (np *PubNameAndPassword) genNewClient(persistStatePath string) (Client, err
}

// PubKey returns the ed25519.PublicKey derived from the password
func (np *PubNameAndPassword) PubKey() (ed25519.PublicKey, error) {
func (np *PubNameAndPassword) PubKey() (e4crypto.Ed25519PublicKey, error) {
key, err := e4crypto.Ed25519PrivateKeyFromPassword(np.Password)
if err != nil {
return nil, fmt.Errorf("failed to create ed25519 key from password: %v", err)
}

edKey, ok := key.Public().(ed25519.PublicKey)
edKey, ok := ed25519.PrivateKey(key).Public().(ed25519.PublicKey)
if !ok {
return nil, errors.New("failed to cast key to ed25519.PublicKey")
}
Expand Down
15 changes: 8 additions & 7 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ func TestProtectUnprotectCommandsPubKey(t *testing.T) {
t.Fatalf("Failed to generate ed25519 key: %v", err)
}

c2PublicCurveKey, c2PrivateCurveKey, err := e4crypto.RandomCurve25519Keys()
c2PrivateCurveKey := e4crypto.RandomKey()
c2PublicCurveKey, err := curve25519.X25519(c2PrivateCurveKey, curve25519.Basepoint)
if err != nil {
t.Fatalf("Failed to generate curve25519 keys: %v", err)
}
Expand Down Expand Up @@ -635,7 +636,7 @@ func TestCommandsSymClient(t *testing.T) {

assertClientTopicKey(t, true, c, topicHash, topicKey)

removeTopicCmd := []byte{RemoveTopic.ToByte()}
removeTopicCmd := []byte{RemoveTopic}
removeTopicCmd = append(removeTopicCmd, topicHash...)

protectedRemoveTopicCmd, err := e4crypto.ProtectSymKey(removeTopicCmd, clientKey)
Expand Down Expand Up @@ -712,7 +713,7 @@ func TestCommandsSymClient(t *testing.T) {
}

// Reset topics
resetTopicCmd := []byte{ResetTopics.ToByte()}
resetTopicCmd := []byte{ResetTopics}
protectedResetCmd, err := e4crypto.ProtectSymKey(resetTopicCmd, clientKey)
if err != nil {
t.Fatalf("Failed to protect command: %v", err)
Expand All @@ -737,7 +738,7 @@ func TestCommandsSymClient(t *testing.T) {
assertClientTopicKey(t, false, c, topicHash, topicKey)

// SetIDKey
setIDKeyCmd := []byte{SetIDKey.ToByte()}
setIDKeyCmd := []byte{SetIDKey}

newClientKey := e4crypto.RandomKey()
setIDKeyCmd = append(setIDKeyCmd, newClientKey...)
Expand Down Expand Up @@ -768,7 +769,7 @@ func TestCommandsSymClient(t *testing.T) {
t.Fatal("Expected an error with a command protected with old key")
}

setPubKeyCmd := []byte{SetPubKey.ToByte()}
setPubKeyCmd := []byte{SetPubKey}
pubKey, _, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("Failed to generate pubkey: %v", err)
Expand Down Expand Up @@ -796,7 +797,7 @@ func TestCommandsSymClient(t *testing.T) {
}

// RemovePubKey
removePubKeyCmd := []byte{RemovePubKey.ToByte()}
removePubKeyCmd := []byte{RemovePubKey}
removePubKeyCmd = append(removePubKeyCmd, pubKeyID...)

protectedRemovePubKeyCmd, err := e4crypto.ProtectSymKey(removePubKeyCmd, newClientKey)
Expand All @@ -818,7 +819,7 @@ func TestCommandsSymClient(t *testing.T) {
}

// ResetPubKeys
resetPubKeyCmd := []byte{ResetPubKeys.ToByte()}
resetPubKeyCmd := []byte{ResetPubKeys}

protectedResetPubKeyCmd, err := e4crypto.ProtectSymKey(resetPubKeyCmd, newClientKey)
if err != nil {
Expand Down
10 changes: 6 additions & 4 deletions cmd/e4keygen/e4keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"log"
"os"

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

e4crypto "github.com/teserakt-io/e4go/crypto"
Expand Down Expand Up @@ -61,15 +62,16 @@ func main() {
case KeyTypeEd25519:
pubKey, privKey, err = ed25519.GenerateKey(nil)
if err != nil {
log.Fatalf("failed to generate ed25519 key: %v\n", err)
log.Fatalf("Failed to generate ed25519 key: %v\n", err)
}
case KeyTypeCurve25519:
pubKey, privKey, err = e4crypto.RandomCurve25519Keys()
privKey = e4crypto.RandomKey()
pubKey, err = curve25519.X25519(privKey, curve25519.Basepoint)
if err != nil {
log.Fatalf("failed to generate curve25519 key: %v\n", err)
log.Fatalf("Failed to generate curve25519 key: %v\n", err)
}
default:
log.Fatalf("unknown key type: %s\n", keyType)
log.Fatalf("Unknown key type: %s\n", keyType)
}

if err := writeKey(privKey, pubKey, out, force); err != nil {
Expand Down
36 changes: 11 additions & 25 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@ import (
e4crypto "github.com/teserakt-io/e4go/crypto"
)

// Command is a command sent by C2 to a client. This is a sequence of bytes, starting from a Command, followed by the command arguments.
// Such command message must then be protected using the client key, before being passed to the client's Unprotect() method. The command will
// then be unprotected, and processed.
type Command int

// List of supported commands
const (
// RemoveTopic command allows to remove a topic key from the client.
// It expects a topic hash as argument
RemoveTopic Command = iota
RemoveTopic byte = iota
// ResetTopics allows to clear out all the topics on a client.
// It doesn't have any argument
ResetTopics
Expand All @@ -54,29 +49,20 @@ const (

// UnknownCommand must stay the last element. It's used to
// know if a Command is out of range
UnknownCommand
UnknownCommand = 0xFF
)

var (
// ErrInvalidCommand is returned when trying to process an unsupported command
ErrInvalidCommand = errors.New("invalid command")
)

// ToByte converts a command into its byte representation
// A value of 255 is returned when the command is out of range
func (c Command) ToByte() byte {
if c < RemoveTopic || c >= UnknownCommand {
return 255
}
return byte(c)
}

// processCommand will attempt to parse given command
// and extract arguments to call expected Client method
func processCommand(client Client, payload []byte) error {
cmd, blob := payload[0], payload[1:]

switch Command(cmd) {
switch cmd {
case RemoveTopic:
if len(blob) != e4crypto.HashLen {
return errors.New("invalid RemoveTopic length")
Expand Down Expand Up @@ -131,14 +117,14 @@ func CmdRemoveTopic(topic string) ([]byte, error) {
return nil, errors.New("topic must not be empty")
}

cmd := append([]byte{RemoveTopic.ToByte()}, e4crypto.HashTopic(topic)...)
cmd := append([]byte{RemoveTopic}, e4crypto.HashTopic(topic)...)

return cmd, nil
}

// CmdResetTopics creates a command to remove all topic keys stored on the client
func CmdResetTopics() ([]byte, error) {
return []byte{ResetTopics.ToByte()}, nil
return []byte{ResetTopics}, nil
}

// CmdSetIDKey creates a command to set the client private key to the given key
Expand All @@ -147,7 +133,7 @@ func CmdSetIDKey(key []byte) ([]byte, error) {
return nil, fmt.Errorf("invalid key length, got %d, wanted %d", keyLen, e4crypto.KeyLen)
}

cmd := append([]byte{SetIDKey.ToByte()}, key...)
cmd := append([]byte{SetIDKey}, key...)

return cmd, nil
}
Expand All @@ -163,7 +149,7 @@ func CmdSetTopicKey(topicKey []byte, topic string) ([]byte, error) {
return nil, errors.New("topic must not be empty")
}

cmd := append([]byte{SetTopicKey.ToByte()}, topicKey...)
cmd := append([]byte{SetTopicKey}, topicKey...)
cmd = append(cmd, e4crypto.HashTopic(topic)...)

return cmd, nil
Expand All @@ -175,19 +161,19 @@ func CmdRemovePubKey(name string) ([]byte, error) {
return nil, errors.New("name must not be empty")
}

cmd := append([]byte{RemovePubKey.ToByte()}, e4crypto.HashIDAlias(name)...)
cmd := append([]byte{RemovePubKey}, e4crypto.HashIDAlias(name)...)

return cmd, nil
}

// CmdResetPubKeys creates a command to removes all public keys from the client
func CmdResetPubKeys() ([]byte, error) {
return []byte{ResetPubKeys.ToByte()}, nil
return []byte{ResetPubKeys}, nil
}

// CmdSetPubKey creates a command to set a given public key,
// identified by given name on the client
func CmdSetPubKey(pubKey ed25519.PublicKey, name string) ([]byte, error) {
func CmdSetPubKey(pubKey e4crypto.Ed25519PublicKey, name string) ([]byte, error) {
if g, w := len(pubKey), ed25519.PublicKeySize; g != w {
return nil, fmt.Errorf("invalid public key length, got %d, wanted %d", g, w)
}
Expand All @@ -196,7 +182,7 @@ func CmdSetPubKey(pubKey ed25519.PublicKey, name string) ([]byte, error) {
return nil, errors.New("name must not be empty")
}

cmd := append([]byte{SetPubKey.ToByte()}, pubKey...)
cmd := append([]byte{SetPubKey}, pubKey...)
cmd = append(cmd, e4crypto.HashIDAlias(name)...)

return cmd, nil
Expand Down
18 changes: 9 additions & 9 deletions commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestCmdRemoveTopic(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{RemoveTopic.ToByte()}, e4crypto.HashTopic(topic)...)
expectedCmd := append([]byte{RemoveTopic}, e4crypto.HashTopic(topic)...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
Expand All @@ -82,7 +82,7 @@ func TestCmdResetTopics(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := []byte{ResetTopics.ToByte()}
expectedCmd := []byte{ResetTopics}
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestCmdSetIDKey(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{SetIDKey.ToByte()}, expectedKey...)
expectedCmd := append([]byte{SetIDKey}, expectedKey...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func TestCmdSetTopicKey(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{SetTopicKey.ToByte()}, expectedKey...)
expectedCmd := append([]byte{SetTopicKey}, expectedKey...)
expectedCmd = append(expectedCmd, e4crypto.HashTopic(expectedTopic)...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
Expand Down Expand Up @@ -178,7 +178,7 @@ func TestCmdRemovePubKey(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{RemovePubKey.ToByte()}, e4crypto.HashIDAlias(expectedName)...)
expectedCmd := append([]byte{RemovePubKey}, e4crypto.HashIDAlias(expectedName)...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
Expand All @@ -196,7 +196,7 @@ func TestCmdResetPubKeys(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := []byte{ResetPubKeys.ToByte()}
expectedCmd := []byte{ResetPubKeys}
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
}
Expand Down Expand Up @@ -243,7 +243,7 @@ func TestCmdSetPubKey(t *testing.T) {
t.Fatalf("invalid command length, got %d, wanted %d", got, want)
}

expectedCmd := append([]byte{SetPubKey.ToByte()}, expectedKey...)
expectedCmd := append([]byte{SetPubKey}, expectedKey...)
expectedCmd = append(expectedCmd, e4crypto.HashIDAlias(expectedName)...)
if !bytes.Equal(cmd, expectedCmd) {
t.Fatalf("invalid command, got %v, wanted %v", cmd, expectedCmd)
Expand All @@ -253,8 +253,8 @@ 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.ToByte() != 255 {
t.Fatalf("expected unknown command byte to be %d, got %d", 255, UnknownCommand.ToByte())
if UnknownCommand != 255 {
t.Fatalf("expected unknown command byte to be %d, got %d", 255, UnknownCommand)
}
})
}
Loading

0 comments on commit d4e2f71

Please sign in to comment.