GithubHelp home page GithubHelp logo

teserakt-io / e4go Goto Github PK

View Code? Open in Web Editor NEW
21.0 9.0 1.0 453 KB

Go library of Teserakt's E4 end-to-end security protocol

Home Page: https://teserakt.io/e4

License: Apache License 2.0

Shell 1.89% Go 98.11%
encryption iot mqtt key-management key-distribution end-to-end-encryption

e4go's Introduction

alt text

GoDoc Go

Introduction

This repository provides the e4 Go package, the client library for Teserakt's E4, and end-to-end encryption and key management framework for MQTT and other publish-subscribe protocols.

The e4 package defines a Client object that has a minimal interface, making its integration straightforward via the following methods:

  • ProtectMessage(payload []byte, topic string) takes a cleartext payload to protect and the associated topic, and returns a []byte that is the payload encrypted and authenticated with the topic's key.

  • Unprotect(protected []byte, topic string) takes a protected payload and attempts to decrypt and verify it. If topic is the special topic reserved for control messages, then the control message is processed and the client's state updated accordingly.

Note that we talk of message protection instead of just encryption because the protection operation includes also authentication and replay defense. The unprotection operation thus involves decryption and additional checks, and includes the processing of control messages sent by the server.

E4's server (C2) is necessary to send control messages and manage a fleet of clients through GUIs, APIs, and automation components. The server can for example deploy key rotation policies, grant and revoke rights, and enable forward secrecy.

Please contact us to request access to a private instance of the server, or test the limited public version. Without the C2 server, the E4 client library can be used to protect messages using static keys, manually managed.

Using our client application

To try E4 without writing your own application, we created a simple interactive client application that you can use in combination with our public demo server interface. You can directly download the client's binary for your platform or build it yourself, and then follow the instructions in the client's README.

Creating a client

The following instructions assume that your program imports e4 as follows:

    import e4 "github.com/teserakt-io/e4go"

The E4 protocol supports both symmetric key and public-key mode. Depending on the mode, different functions should be used to instantiate a client:

Symmetric-key client

A symmetric-key client can be created from a 16-byte identifier (type []byte), a 32-byte key (type []byte), and an e4.ReadWriteSeeker implementation, used to persist the client's state (see client storage section for details):

    client, err := e4.NewClient(&e4.SymIDAndKey{ID: id, Key: key}, store)

A symmetric-key client can also be created from a name (string of arbitrary length) and a password (string of a least 16 characters), as follows:

    client, err := e4.NewClient(&e4.SymNameAndPassword{Name: name, Password: password}, store)

The latter is a wrapper over NewSymKeyClient() that creates the ID by hashing name with SHA-3-256, and deriving a key using Argon2.

Public-key client

A public-key client can be created from a 16-byte identifier (type []byte), an Ed25519 private key (type ed25519.PrivateKey), an e4.ReadWriteSeeker implementation, which will be used to store the client's state (see client storage section for details), and a Curve25519 public key (32-byte []byte):

client, err := e4.NewClient(&e4.PubIDAndKey{ID:id, Key: key, C2PubKey: c2PubKey}, store)

Compared to the symmetric-key mode, and additional argument is c2PubKey, the public key of the C2 server that sends control messages.

A public-key client can also be created from a name (string of arbitrary length) and a password (string of a least 16 characters), as follows:

client, err := e4.NewClient(&e4.PubNameAndPassword{Name:name, Password: password, C2PubKey: c2PubKey}, store)

The Ed25519 private key is then created from a seed that is derived from the password using Argon2. The Ed25519 public key can also be retrieved:

config := &e4.PubNameAndPassword{Name:name, Password: password, C2PubKey: c2PubKey}
pubKey, err := config.PubKey()

From a saved state

A client instance can be recovered using the LoadClient() helper given an e4.ReadWriteSeeker implementation::

    client, err := e4.LoadClient(store)

Note that a client's state is automatically saved to the provided store when the client is created, and every time its state changes, and therefore does not need be manually saved.

Client storage

E4 client offer a way to persist its internal state, allowing to shut it down and reload without having to retransmit all the keys, by providing an e4.ReadWriteSeeker implementation to the client. This interface is compatible with any io.ReadWriteSeeker, such as the os.File type, which should be the most common option. But it also allows custom implementations for platforms where filesystem isn't available, see the e4.NewInMemoryStore([]byte) we provide as an example of custom storage implementation.

Integration instructions

To integrate E4 into your application, the protect/unprotect logic needs be added between the network layer and the application layer when transmitting/receiving a message.

This section provides further instructions related to error handling and to the special case of control messages received from the C2 server.

Note that E4 is essentially an application security layer, therefore it processes the payload of a message (such as an MQTT payload), excluding header fields. References to "messages" below therefore refer to payload data (or application message),as opposed to the network-level message.

Receiving a message

Assume that you receive messages over MQTT or Kafka, and have topics and payload defined as

    var topic string
    var message []byte

Having instantiated a client, you can then unprotect the message as follows:

    plaintext, err := client.Unprotect(message, topic)
    if err != nil {
        // your error reporting here
    }

If you receive no error, plaintext may still be nil. This happens when E4 has processed a control message, that is, a message sent by the C2 server, for example to provision or delete a topic key. In this case, you do not need to act on the message, since E4 has already processed it. If you want to detect this case you can test for

    if len(plainText) == 0 { ... }

or alternatively

    if client.IsReceivingTopic(topic)

which indicates a message on E4's control channel. You should not have to parse E4's messages yourself. Control messages are thus deliberately not returned to users.

If plaintext is not nil and err is nil, your application can proceed with the unprotected, plaintext message.

Transmitting a message

To protect a message to be transmitted, suppose say that you have the topic and payload defined as:

    var topic string
    var message []byte

You can then use the Protect method from the client instance as follows:

    protected, err := client.Protect(message, topic)
    if err != nil {
        // your error reporting here
    }

Handling errors

All errors should be reported, and the plaintext and protected values discarded upon an error, except potentially in one case: if you receive an ErrTopicKeyNotFound error from ProtectMessage() or Unprotect(), it is because the client does not have the key for this topic. Therefore,

  • When transmitting a message, your application can either discard the message to be sent, or choose to transmit it in clear.

  • When receiving a message, your application can either discard the message (for example if all messages are assumed to be encrypted in your network), or forward the message to the application (if you call Unprotect() for all messages yet tolerate the receiving of unencrypted messages over certain topics, which thus don't have a topic key).

In order to have the key associated to a certain topic, you must instruct the C2 to deliver said topic key to the client.

Key generation

To ease key creation, we provide a key generation application that you can use to generate symmetric, Ed25519 or Curve25519 keys needed for E4 operations. You can download the binary for your platform or build it yourself, and then follow the instructions in the keygen README.

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. On an environment having an Android SDK and NDK available, an Android AAR package can be generated invoking the following script:

./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:

import java.io.RandomAccessFile

import io.teserakt.e4.E4
import io.teserakt.e4.SymNameAndPassword
import io.teserakt.crypto.Crypto

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

val store = FileStore(filesDir.absolutePath + "/" + cfg.name + ".json")
val client = E4.newClient(cfg, store)

// 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)

Here We are using a custom file storage implemented as such:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import io.teserakt.e4.Store;

public class FileStore implements Store {
    private static final int SEEK_START = 0;
    private static final int SEEK_CURRENT = 1;
    private static final int SEEK_END = 2;

    private RandomAccessFile file;

    public FileStore(String filepath) throws FileNotFoundException {
        this.file = new RandomAccessFile(filepath, "rw");
    }

    public long read(byte[] buf) throws IOException {
        return this.file.read(buf);
    }

    public long write(byte[] buf) throws IOException {
        this.file.write(buf);
        return buf.length;
    }

    public long seek(long offset, long whence) throws Exception {
        long abs;
        switch((int)whence) {
            case SEEK_START:
                abs = offset;
                break;
            case SEEK_CURRENT:
                abs = this.file.getChannel().position() + offset;
                break;
            case SEEK_END:
                abs = this.file.length() + offset;
                break;
            default:
                throw new Exception("invalid whence");
        }
        if (abs < 0) {
            throw new Exception("negative position");
        }

        this.file.getChannel().position(abs);
        return abs;
    }
}

Contributing

Before contributing, please read our CONTRIBUTING guide.

Security

To report a security vulnerability (or potential vulnerability where private discussion is preferred) see SECURITY.

Support

To request support, please contact [email protected].

Intellectual property

e4go is copyright (c) Teserakt AG 2018-2020, and released under Apache 2.0 License (see LICENCE).

e4go's People

Contributors

daemon63 avatar diagprov avatar odeke-em avatar veorq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

dolanor-galaxy

e4go's Issues

Spurious use of hex.Encode for hashmaps

Currently we're using hex encoding in order to use hashes as keys in a hashmap, e.g.

e4go/client.go

Line 427 in d0f4bc6

topicKeyTs, ok := c.TopicKeys[hashOfHash]

However, this isn't actually necessary, as the following code example shows:

package main

import (
    "fmt"
    "golang.org/x/crypto/sha3"
)

func main() {

    keymap := make(map[string]string)

    data := "some data"
    topic := "some topic"
    topichash := sha3.Sum256([]byte(topic))
    keymap[string(topichash[:])] = data
    fmt.Println(keymap)
}

Here the hash is directly encoded as a string type, albeit not one you can print easily. In this, no conversion between formats is required.

We probably want to encode these keys for json output, but during normal operation I'm not sure we need it. What do you think @odeke-em ? (I would key a hashmap in C by passing the raw bytes through the hash function for the hashmap).

all: package name should be `e4` and not `e4go`

The repository basename is e4go and that's alright since we are delineating between other languages i.e. e4python, e4java, e4swift etc. However, we are already writing Go programs and having go as a suffix for a package in Go is non-idiomatic.

Let's fix this to

package e4

example_test.go: attempt to always use public APIs in examples to make for standalone copy and pastable code

Currently in the example_test.go file, our package is e4go and since it is us using the package we can get use the public API without importing and when rendered by godoc, here is what an example looks like:
Screen Shot 2019-12-04 at 4 01 09 PM

Notice that highlighted and unexported NewPubKeyClient? Really users should be able to copy and paste our examples in their own repos without having to then tediously go perform exports. It also means that we are putting ourselves to task and using the public api.

The fix to this is:
a) Making the package e4go_test
b) Making the imports as though an outside user were using them

which will then look like
Screen Shot 2019-12-04 at 4 04 18 PM

all: Apache 2.0 license header isn't being properly applied as the first item in each file except if with build tags

We need to apply the Apache license header as the first element in the file with the only exception of adding building tags

diff --git a/client.go b/client.go
index 6bbe257..1d62e15 100644
--- a/client.go
+++ b/client.go
@@ -1,4 +1,17 @@
+// Copyright 2018-2019-2020 Teserakt AG
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
 //
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Package e4go provides a e4 client implementation and libraries.
 //
 // It aims to be quick and easy to integrate in IoT devices applications
@@ -30,19 +43,6 @@
 // See commands.go for the list of available commands and their respective parameters.
 package e4go
 
-// Copyright 2018-2019-2020 Teserakt AG
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 
 import (
 	"bytes"

and for example, this company is a heavy user of the Apache 2 license and has quite the authority on OSS and this is how they use it https://github.com/googleapis/google-cloud-go/blob/bb047ca08b92e3454eae16a3b8dc6830ba3b8bf8/firestore/client.go#L1-L15

NewSymKeyClient*: two constructors could be made into one while making a clear and simpler API

Two constructors exist for NewClient

func NewSymKeyClient(id []byte, key []byte, persistStatePath string) (Client, error)
func NewSymKeyClientPretty(name string, password string, persistStatePath string) (Client, error)

and one has to read through the differences, but also the Pretty suffix on NewSymKeyClientPretty wasn't apparent to me.

I think we can reduce our API surface while making it easy for customers to pick which one to use and we can do that by passing in a struct called ClientConfig which will take either NameAndPassword or IDAndKey for example

func NewSymKeyClient(config *ClientConfig, persistStatePath string) (Client, error)

and the usage will be

func ExampleNewSymKeyClient_idAndKey() {
        client, err := NewSymKeyClient(&ClientConfig{
                IDAndKey: &IDAndKey{
                        ID:  []byte("525269a1-8d8e-4684-8d49-3b55f9d48df6"),
                        Key: crypto.RandomKey(),
                },
        }, "./symClient.json")
        if err != nil {
                panic(err)
        }

        protectedMessage, err := client.ProtectMessage([]byte("very secret message"), "topic/name")
        if err != nil {
                panic(err)
        }

        fmt.Printf("Protected message: %v", protectedMessage)
}

func ExampleNewSymKeyClient_namePassword() {
        client, err := NewSymKeyClient(&ClientConfig{
                NameAndPassword: &NameAndPassword{
                        Name:     "freight-train-lock-89111",
                        Password: "^***TOP SECRET***$",
                },
        }, "./symClient.json")

        if err != nil {
                panic(err)
        }

        protectedMessage, err := client.ProtectMessage([]byte("very secret message"), "topic/name")
        if err != nil {
                panic(err)
        }

        fmt.Printf("Protected message: %v", protectedMessage)
}

which also keeps the parameters short, but alternates clearly to our users that they can either use:
a) Password and Name
b) ID and Key

Abstract storage away from filesystem

Current implementation force a filesystem requirement in:

func LoadClient(persistStatePath string) (Client, error)
func NewClient(config ClientConfig, persistStatePath string) (Client, error)

We need to be able to provide other kind of storage, such as database or key value store, or even provide ways to implement custom ones.

Precompute shared secret with C2

Since the C2 key often remains unchanged, as does the device key, we should precompute:

sharedkey = sha3(curve25519(device_sk, c2_pk))

and store the shared key, to avoid the need for computations on receipt of every control message.

Logic may need to exist to force this precomputation if the device public key is changed by the C2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.