From 278458468a9909ec5aeb6cc34d946c56aea72a35 Mon Sep 17 00:00:00 2001 From: Ben Fiedler Date: Thu, 14 Jan 2021 15:32:37 +0100 Subject: [PATCH] Add ID() and PublicKey() methods --- client/client.go | 40 ++++++++++++++++++++++++++++++++-------- client/client_test.go | 14 ++++++++++++-- client/crypto.go | 12 +++++++++++- client/crypto_test.go | 5 +++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/client/client.go b/client/client.go index 2df6c15..eb25223 100644 --- a/client/client.go +++ b/client/client.go @@ -37,7 +37,7 @@ const ( var ( // Caller errors ErrInvalidPhoneNumber = errors.New("invalid phone number, must consist of at most 15 digits") - ErrIDLength = errors.New("id must be exactly 8 bytes long") + ErrIDLength = errors.New("id must be exactly 8 bytes long") // API errors ErrInvalidRequest = errors.New("invalid recipient or identity not set up for chosen mode (simple/e2e)") @@ -50,7 +50,7 @@ var ( ErrTemporary = errors.New("temporary API failure") ErrUnknown = errors.New("unknown API error") - phoneNumberInverted = regexp.MustCompile("[^0-9]+") + phoneNumberInverted = regexp.MustCompile("[^0-9]+") emailHMACKey = []byte{0x30, 0xa5, 0x50, 0x0f, 0xed, 0x97, 0x01, 0xfa, 0x6d, 0xef, 0xdb, 0x61, 0x08, 0x41, 0x90, 0x0f, 0xeb, 0xb8, 0xe4, 0x30, @@ -61,6 +61,8 @@ var ( ) type Client interface { + ID() string + // Fetches the remaining credits for the ID associated with the Client. GetRemainingCredits() (int, error) @@ -78,7 +80,6 @@ type client struct { id ThreemaID apiSecret string - privKey PrivateKey httpClient *http.Client @@ -96,10 +97,13 @@ type SimpleClient struct { } // An E2EClient has a private key to sign and encrypt messages with. +// +// It automatically caches ID->PubKey lookups as adivsed in the Threema.Gateway +// documentation. type E2EClient struct { client - privKey PrivateKey + privateKey PrivateKey cacheMu *sync.RWMutex publicKeyCache map[ThreemaID]cacheEntry @@ -139,20 +143,40 @@ func NewSimpleClient(id ThreemaID, apiSecret string) (*SimpleClient, error) { }, err } -func NewE2EClient(id ThreemaID, apiSecret string, privKey PrivateKey) (*E2EClient, error) { +func NewE2EClient(id ThreemaID, apiSecret string, privateKey PrivateKey) (*E2EClient, error) { c, err := newClient(id, apiSecret) return &E2EClient{ - client: *c, - privKey: privKey, + client: *c, + privateKey: privateKey, cacheMu: &sync.RWMutex{}, publicKeyCache: make(map[ThreemaID]cacheEntry), }, err } +// Returns the client's ID +func (c *client) ID() string { + return c.id +} + +// Returns the client's hex-encoded public key +// +// TODO: not sure if we maybe even want to return the raw bytes? +func (c *E2EClient) PublicKey() string { + publicKey := computePublicKey(c.privateKey) + encodedPublicKey := make([]byte, hex.EncodedLen(len(publicKey))) + _ = hex.Encode(encodedPublicKey, publicKey[:]) + return string(encodedPublicKey) +} + +// Set the base URL for the Threema.Gateway server +func (c *client) SetGatewayURL(url string) { + c.gatewayUrl = url +} + // Set the http.Client to be used -func (c *client) SetClient(httpClient *http.Client) { +func (c *client) SetHTTPClient(httpClient *http.Client) { c.httpClient = httpClient } diff --git a/client/client_test.go b/client/client_test.go index 9364fec..aa56a51 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -17,10 +17,20 @@ var ( testServer *httptest.Server + // This is a valid keypair, i.e. computePublicKey(privateKey) == publicKey privateKey = [32]byte{0x93, 0x13, 0x2c, 0x74, 0xfd, 0x3e, 0x63, 0x1d, 0x78, 0x18, 0x6e, 0x8b, 0x8a, 0x8a, 0xc1, 0x39, 0x6d, 0x7a, 0x47, 0x73, 0x38, 0xd3, 0x42, 0xd1, 0x67, 0xa5, 0xa7, 0x2b, 0xb9, 0xba, 0x51, 0xc6} publicKey = [32]byte{0x9f, 0xea, 0xd8, 0x30, 0x1b, 0x0e, 0x13, 0xfe, 0xa8, 0x56, 0x9e, 0x7e, 0xc4, 0xe0, 0x4e, 0x80, 0x86, 0xb2, 0x16, 0x6e, 0x4b, 0xf7, 0xef, 0x5f, 0xee, 0x0d, 0xd9, 0xc0, 0xa4, 0x16, 0xdc, 0x6c} ) +func TestID(t *testing.T) { + t.Run("Simple", func(t *testing.T) { assert.Equal(t, simpleClient.ID(), "*SIMPTST") }) + t.Run("E2E", func(t *testing.T) { assert.Equal(t, e2eClient.ID(), "*E2ETEST") }) +} + +func TestPublicKey(t *testing.T) { + assert.Equal(t, "9fead8301b0e13fea8569e7ec4e04e8086b2166e4bf7ef5fee0dd9c0a416dc6c", e2eClient.PublicKey()) +} + func TestLookupIDByPhoneHash(t *testing.T) { t.Parallel() @@ -257,13 +267,13 @@ func setup() { if err != nil { panic(err) } - e2eClient.gatewayUrl = testServer.URL + e2eClient.SetGatewayURL(testServer.URL) simpleClient, err = NewSimpleClient("*SIMPTST", "apiSecretSimple") if err != nil { panic(err) } - simpleClient.gatewayUrl = testServer.URL + simpleClient.SetGatewayURL(testServer.URL) } func mockLookupPhoneHash(w http.ResponseWriter, r *http.Request) { diff --git a/client/crypto.go b/client/crypto.go index c2d9407..4c24793 100644 --- a/client/crypto.go +++ b/client/crypto.go @@ -6,6 +6,7 @@ import ( "math/big" "strings" + "golang.org/x/crypto/curve25519" "golang.org/x/crypto/nacl/box" ) @@ -18,6 +19,15 @@ const ( DeliveryReceipt = 0x80 // Unsupported, TODO ) +// Computes the public key belonging to privateKey. +// +// Implementation taken from /x/crypto/nacl/box.GenerateKey +func computePublicKey(privateKey PrivateKey) PublicKey { + var publicKey PublicKey + curve25519.ScalarBaseMult(&publicKey, &privateKey) + return publicKey +} + // Hashes the given phone number using the defined HMAC function. Returns the // hex-encoded hash as string. func (c *client) hashPhone(phoneNumber string) (string, error) { @@ -75,7 +85,7 @@ func (c *E2EClient) encryptTextMessage(pubKey PublicKey, msg string) ([]byte, No return nil, nonce, err } - out := box.Seal(nil, rawMsg, &nonce, &pubKey, &c.privKey) + out := box.Seal(nil, rawMsg, &nonce, &pubKey, &c.privateKey) return out, nonce, nil } diff --git a/client/crypto_test.go b/client/crypto_test.go index abe5aa0..8d4ba87 100644 --- a/client/crypto_test.go +++ b/client/crypto_test.go @@ -19,6 +19,11 @@ func init() { } } +func TestComputePublicKey(t *testing.T) { + got := computePublicKey(privateKey) + assert.Equal(t, publicKey, got) +} + // Test case available on the Threema Gateway documentation // https://gateway.threema.ch/en/developer/api func TestHashPhone(t *testing.T) { -- 2.45.2