From 9e6ef104f05d380bd4ffc5065c22b98d8e77cc3a Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 29 Mar 2022 04:45:21 +0200 Subject: [PATCH 1/9] run license check before linter Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- Makefile | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 4954b27..351950c 100644 --- a/Makefile +++ b/Makefile @@ -22,17 +22,16 @@ fmt: @gofumpt -w -extra . @gci write --Section Standard --Section Default --Section "Prefix($(shell go list -m))" . -.PHONY: lint - -lint: - @echo "Linting ..." - @if golangci-lint run --config=./.github/.golangci.yml ./...; then echo "Linting OK"; else return 1; fi; - .PHONY: license license: @echo "Checking License headers ..." @if addlicense -check -v -f .github/licence-header.tmpl *; then echo "License headers OK"; else return 1; fi; +.PHONY: lint +lint: license + @echo "Linting ..." + @if golangci-lint run --config=./.github/.golangci.yml ./...; then echo "Linting OK"; else return 1; fi; + .PHONY: test test: @echo "Running all tests ..." From 537662d1591c7b06af5f11068dcb4f3dd8511154 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 29 Mar 2022 04:45:39 +0200 Subject: [PATCH 2/9] add license header Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- deserializer.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deserializer.go b/deserializer.go index 7f8c148..a2eeafe 100644 --- a/deserializer.go +++ b/deserializer.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + package opaque import ( From e2a828092269f04a889e7c3b99729f6450e4b8b4 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Sun, 3 Apr 2022 01:02:57 +0200 Subject: [PATCH 3/9] refactor test files Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- tests/client_test.go | 270 ++++++++++++ tests/deserializer_test.go | 135 ++++++ tests/encoding_test.go | 172 ++++++++ tests/failing_test.go | 850 ------------------------------------- tests/helper_test.go | 215 ++++++++++ tests/i2osp_test.go | 187 -------- tests/opaque_test.go | 167 ++++++++ tests/oprf_test.go | 5 + tests/server_test.go | 256 +++++++++++ 9 files changed, 1220 insertions(+), 1037 deletions(-) create mode 100644 tests/client_test.go create mode 100644 tests/deserializer_test.go delete mode 100644 tests/failing_test.go create mode 100644 tests/helper_test.go delete mode 100644 tests/i2osp_test.go create mode 100644 tests/server_test.go diff --git a/tests/client_test.go b/tests/client_test.go new file mode 100644 index 0000000..90aefca --- /dev/null +++ b/tests/client_test.go @@ -0,0 +1,270 @@ +package opaque_test + +import ( + "strings" + "testing" + + "github.com/bytemare/opaque" + "github.com/bytemare/opaque/internal" + "github.com/bytemare/opaque/internal/encoding" + "github.com/bytemare/opaque/internal/tag" +) + +/* + The following tests look for failing conditions. +*/ + +func TestClientRegistrationFinalize_InvalidPks(t *testing.T) { + /* + Invalid data sent to the client + */ + credID := internal.RandomBytes(32) + + for _, conf := range confs { + client, _ := conf.Conf.Client() + server, _ := conf.Conf.Server() + _, pks := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + r1 := client.RegistrationInit([]byte("yo")) + + pk, err := server.GetConf().Group.NewElement().Decode(pks) + if err != nil { + panic(err) + } + r2 := server.RegistrationResponse(r1, pk, credID, oprfSeed) + + // message length + badr2 := internal.RandomBytes(15) + expected := "invalid message length" + if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for empty server public key - got %v", err) + } + + // invalid data + badr2 = encoding.Concat(getBadElement(t, conf), pks) + expected = "invalid OPRF evaluation" + if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for empty server public key - got %v", err) + } + + // nil pks + expected = "invalid server public key" + badr2 = encoding.Concat(r2.Serialize()[:client.GetConf().OPRFPointLength], getBadElement(t, conf)) + if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid server public key - got %v", err) + } + } +} + +func TestClientFinish_BadEvaluation(t *testing.T) { + /* + Oprf finalize : evaluation deserialization // element decoding + */ + for _, conf := range confs { + client, _ := conf.Conf.Client() + _ = client.LoginInit([]byte("yo")) + r2 := encoding.Concat( + getBadElement(t, conf), + internal.RandomBytes( + client.GetConf().NonceLen+client.GetConf().AkePointLength+client.GetConf().EnvelopeSize, + ), + ) + badKe2 := encoding.Concat( + r2, + internal.RandomBytes(client.GetConf().NonceLen+client.GetConf().AkePointLength+client.GetConf().MAC.Size()), + ) + + expected := "invalid OPRF evaluation" + if _, err := client.Deserialize.KE2(badKe2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid evaluated element - got %v", err) + } + } +} + +func TestClientFinish_BadMaskedResponse(t *testing.T) { + /* + The masked response is of invalid length. + */ + credID := internal.RandomBytes(32) + + for _, conf := range confs { + client, _ := conf.Conf.Client() + server, _ := conf.Conf.Server() + sks, pks := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) + + ke1 := client.LoginInit([]byte("yo")) + ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) + + goodLength := encoding.PointLength[client.GetConf().Group] + client.GetConf().EnvelopeSize + expected := "invalid masked response length" + + // too short + ke2.MaskedResponse = internal.RandomBytes(goodLength - 1) + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for short response - got %v", err) + } + + // too long + ke2.MaskedResponse = internal.RandomBytes(goodLength + 1) + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for long response - got %v", err) + } + } +} + +func TestClientFinish_InvalidEnvelopeTag(t *testing.T) { + /* + Invalid envelope tag + */ + credID := internal.RandomBytes(32) + + for _, conf := range confs { + client, _ := conf.Conf.Client() + server, _ := conf.Conf.Server() + sks, pks := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) + + ke1 := client.LoginInit([]byte("yo")) + ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) + + env, _, err := getEnvelope(client, ke2) + if err != nil { + t.Fatal(err) + } + + // tamper the envelope + env.AuthTag = internal.RandomBytes(client.GetConf().MAC.Size()) + clear := encoding.Concat(pks, env.Serialize()) + ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) + + // too short + expected := "recover envelope: invalid envelope authentication tag" + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid envelope mac - got %v", err) + } + } +} + +func cleartextCredentials(clientPublicKey, serverPublicKey, idc, ids []byte) []byte { + if ids == nil { + ids = serverPublicKey + } + + if idc == nil { + idc = clientPublicKey + } + + return encoding.Concat3(serverPublicKey, encoding.EncodeVector(ids), encoding.EncodeVector(idc)) +} + +func TestClientFinish_InvalidKE2KeyEncoding(t *testing.T) { + /* + Tamper KE2 values + */ + credID := internal.RandomBytes(32) + + for _, conf := range confs { + client, _ := conf.Conf.Client() + server, _ := conf.Conf.Server() + sks, pks := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) + + ke1 := client.LoginInit([]byte("yo")) + ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) + // epks := ke2.EpkS + + // tamper epks + offset := client.GetConf().AkePointLength + client.GetConf().MAC.Size() + encoded := ke2.Serialize() + badKe2 := encoding.Concat3(encoded[:len(encoded)-offset], getBadElement(t, conf), ke2.Mac) + expected := "invalid ephemeral server public key" + if _, err := client.Deserialize.KE2(badKe2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid epks encoding - got %q", err) + } + + // tamper PKS + // ke2.EpkS = server.Group.NewElement().Mult(server.Group.NewScalar().Random()) + env, randomizedPwd, err := getEnvelope(client, ke2) + if err != nil { + t.Fatal(err) + } + + badpks := getBadElement(t, conf) + + ctc := cleartextCredentials( + encoding.SerializePoint(rec.RegistrationRecord.PublicKey, client.GetConf().Group), + badpks, + nil, + nil, + ) + authKey := client.GetConf().KDF.Expand( + randomizedPwd, + encoding.SuffixString(env.Nonce, tag.AuthKey), + client.GetConf().KDF.Size(), + ) + authTag := client.GetConf().MAC.MAC(authKey, encoding.Concat(env.Nonce, ctc)) + env.AuthTag = authTag + + clear := encoding.Concat(badpks, env.Serialize()) + ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) + + expected = "invalid server public key" + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid envelope mac - got %q", err) + } + + // replace PKS + fakepks := server.GetConf().Group.Base().Mult(server.GetConf().Group.NewScalar().Random()).Bytes() + clear = encoding.Concat(fakepks, env.Serialize()) + ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) + + expected = "recover envelope: invalid envelope authentication tag" + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid envelope mac - got %q", err) + } + } +} + +func TestClientFinish_InvalidKE2Mac(t *testing.T) { + /* + Invalid server ke2 mac + */ + credID := internal.RandomBytes(32) + + for _, conf := range confs { + client, _ := conf.Conf.Client() + server, _ := conf.Conf.Server() + sks, pks := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) + + ke1 := client.LoginInit([]byte("yo")) + ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) + + ke2.Mac = internal.RandomBytes(client.GetConf().MAC.Size()) + expected := " AKE finalization: invalid server mac" + if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error for invalid epks encoding - got %q", err) + } + } +} + +func TestClientFinish_MissingKe1(t *testing.T) { + expectedError := "missing KE1 in client state" + conf := opaque.DefaultConfiguration() + client, _ := conf.Client() + if _, _, err := client.LoginFinish(nil, nil, nil); err == nil || !strings.EqualFold(err.Error(), expectedError) { + t.Fatalf( + "expected error when calling LoginFinish without pre-existing KE1, want %q, got %q", + expectedError, + err, + ) + } +} diff --git a/tests/deserializer_test.go b/tests/deserializer_test.go new file mode 100644 index 0000000..2aea804 --- /dev/null +++ b/tests/deserializer_test.go @@ -0,0 +1,135 @@ +package opaque_test + +import ( + "errors" + "testing" + + "github.com/bytemare/crypto/group" + + "github.com/bytemare/opaque" + "github.com/bytemare/opaque/internal" + "github.com/bytemare/opaque/internal/encoding" +) + +var errInvalidMessageLength = errors.New("invalid message length for the configuration") + +/* + Message Deserialization +*/ + +func TestDeserializeRegistrationRequest(t *testing.T) { + c := opaque.DefaultConfiguration() + + server, _ := c.Server() + conf := server.GetConf() + length := conf.OPRFPointLength + 1 + if _, err := server.Deserialize.RegistrationRequest(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } + + client, _ := c.Client() + if _, err := client.Deserialize.RegistrationRequest(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } +} + +func TestDeserializeRegistrationResponse(t *testing.T) { + c := opaque.DefaultConfiguration() + + server, _ := c.Server() + conf := server.GetConf() + length := conf.OPRFPointLength + conf.AkePointLength + 1 + if _, err := server.Deserialize.RegistrationResponse(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } + + client, _ := c.Client() + if _, err := client.Deserialize.RegistrationResponse(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } +} + +func TestDeserializeRegistrationRecord(t *testing.T) { + for _, e := range confs { + server, _ := e.Conf.Server() + conf := server.GetConf() + length := conf.AkePointLength + conf.Hash.Size() + conf.EnvelopeSize + 1 + if _, err := server.Deserialize.RegistrationRecord(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } + + badPKu := getBadElement(t, e) + rec := encoding.Concat(badPKu, internal.RandomBytes(conf.Hash.Size()+conf.EnvelopeSize)) + + expect := "invalid client public key" + if _, err := server.Deserialize.RegistrationRecord(rec); err == nil || err.Error() != expect { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", expect, err) + } + + client, _ := e.Conf.Client() + if _, err := client.Deserialize.RegistrationRecord(internal.RandomBytes(length)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) + } + } +} + +func TestDeserializeKE1(t *testing.T) { + c := opaque.DefaultConfiguration() + g := group.Group(c.AKE) + ke1Length := encoding.PointLength[g] + internal.NonceLength + encoding.PointLength[g] + + server, _ := c.Server() + if _, err := server.Deserialize.KE1(internal.RandomBytes(ke1Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } + + client, _ := c.Client() + if _, err := client.Deserialize.KE1(internal.RandomBytes(ke1Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } +} + +func TestDeserializeKE2(t *testing.T) { + c := opaque.DefaultConfiguration() + + client, _ := c.Client() + conf := client.GetConf() + ke2Length := conf.OPRFPointLength + 2*conf.NonceLen + 2*conf.AkePointLength + conf.EnvelopeSize + conf.MAC.Size() + if _, err := client.Deserialize.KE2(internal.RandomBytes(ke2Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } + + server, _ := c.Server() + conf = server.GetConf() + ke2Length = conf.OPRFPointLength + 2*conf.NonceLen + 2*conf.AkePointLength + conf.EnvelopeSize + conf.MAC.Size() + if _, err := server.Deserialize.KE2(internal.RandomBytes(ke2Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } +} + +func TestDeserializeKE3(t *testing.T) { + c := opaque.DefaultConfiguration() + ke3Length := c.MAC.Size() + + server, _ := c.Server() + if _, err := server.Deserialize.KE3(internal.RandomBytes(ke3Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } + + client, _ := c.Client() + if _, err := client.Deserialize.KE3(internal.RandomBytes(ke3Length + 1)); err == nil || + err.Error() != errInvalidMessageLength.Error() { + t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) + } +} diff --git a/tests/encoding_test.go b/tests/encoding_test.go index b3cc382..7ba438c 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -9,6 +9,9 @@ package opaque_test import ( + "bytes" + "encoding/hex" + "fmt" "testing" "github.com/bytemare/opaque/internal/encoding" @@ -43,3 +46,172 @@ func TestDecodeVector(t *testing.T) { t.Fatalf("expected error for short input. Got %q", err) } } + +type i2ospTest struct { + value int + size int + encoded []byte +} + +var I2OSPVectors = []i2ospTest{ + { + 0, 1, []byte{0}, + }, + { + 1, 1, []byte{1}, + }, + { + 255, 1, []byte{0xff}, + }, + { + 256, 2, []byte{0x01, 0x00}, + }, + { + 65535, 2, []byte{0xff, 0xff}, + }, + { + 16770000, 3, []byte{0xff, 0xe3, 0xd0}, + }, + { + 4294960000, 4, []byte{0xff, 0xff, 0xe3, 0x80}, + }, +} + +func TestI2OSP(t *testing.T) { + for i, v := range I2OSPVectors { + t.Run(fmt.Sprintf("%d - %d - %v", v.value, v.size, v.encoded), func(t *testing.T) { + r := encoding.I2OSP(v.value, v.size) + + if !bytes.Equal(r, v.encoded) { + t.Fatalf( + "invalid encoding for %d. Expected '%s', got '%v'", + i, + hex.EncodeToString(v.encoded), + hex.EncodeToString(r), + ) + } + + value := encoding.OS2IP(v.encoded) + if v.value != value { + t.Errorf("invalid decoding for %d. Expected %d, got %d", i, v.value, value) + } + }) + } + + length := -1 + if hasPanic, err := expectPanic(nil, func() { + _ = encoding.I2OSP(1, length) + }); !hasPanic { + t.Fatalf("expected panic with with negative length: %v", err) + } + + length = 0 + if hasPanic, err := expectPanic(nil, func() { + _ = encoding.I2OSP(1, length) + }); !hasPanic { + t.Fatalf("expected panic with with 0 length: %v", err) + } + + length = 5 + if hasPanic, err := expectPanic(nil, func() { + _ = encoding.I2OSP(1, length) + }); !hasPanic { + t.Fatalf("expected panic with length too big: %v", err) + } + + negative := -1 + if hasPanic, err := expectPanic(nil, func() { + _ = encoding.I2OSP(negative, 4) + }); !hasPanic { + t.Fatalf("expected panic with negative input: %v", err) + } + + tooLarge := 1 << 32 + length = 1 + if hasPanic, err := expectPanic(nil, func() { + _ = encoding.I2OSP(tooLarge, length) + }); !hasPanic { + t.Fatalf("expected panic with exceeding value for the length: %v", err) + } + + lengths := map[int]int{ + 100: 1, + 1 << 8: 2, + 1 << 16: 3, + (1 << 32) - 1: 4, + } + + for k, v := range lengths { + r := encoding.I2OSP(k, v) + + if len(r) != v { + t.Fatalf("invalid length for %d. Expected '%d', got '%d' (%v)", k, v, len(r), r) + } + } +} + +func TestOS2IP(t *testing.T) { + // No input + if hasPanic, _ := expectPanic(nil, func() { + _ = encoding.OS2IP(nil) + }); !hasPanic { + t.Fatal("expected panic with nil input") + } + + // Empty input + if hasPanic, _ := expectPanic(nil, func() { + _ = encoding.OS2IP([]byte("")) + }); !hasPanic { + t.Fatal("expected panic with empty input") + } + + // Exceeding input + input := "12345" + if hasPanic, _ := expectPanic(nil, func() { + _ = encoding.OS2IP([]byte(input)) + }); !hasPanic { + t.Fatal("expected panic with big input") + } +} + +func hasPanic(f func()) (has bool, err error) { + err = nil + var report interface{} + func() { + defer func() { + if report = recover(); report != nil { + has = true + } + }() + + f() + }() + + if has { + err = fmt.Errorf("%v", report) + } + + return +} + +func expectPanic(expectedError error, f func()) (bool, string) { + hasPanic, err := hasPanic(f) + + if !hasPanic { + return false, "no panic" + } + + if expectedError == nil { + return true, "" + } + + if err == nil { + return false, "panic but no message" + } + + if err.Error() != expectedError.Error() { + return false, fmt.Sprintf("expected %q, got %q", expectedError, err) + } + + return true, "" +} diff --git a/tests/failing_test.go b/tests/failing_test.go deleted file mode 100644 index a3d9aac..0000000 --- a/tests/failing_test.go +++ /dev/null @@ -1,850 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree or at -// https://spdx.org/licenses/MIT.html - -package opaque_test - -import ( - "crypto" - "crypto/elliptic" - "encoding/hex" - "errors" - "fmt" - "math/big" - "reflect" - "strings" - "testing" - - "github.com/bytemare/crypto/group" - "github.com/bytemare/crypto/ksf" - - "github.com/bytemare/opaque" - "github.com/bytemare/opaque/internal" - "github.com/bytemare/opaque/internal/encoding" - "github.com/bytemare/opaque/internal/keyrecovery" - "github.com/bytemare/opaque/internal/oprf" - "github.com/bytemare/opaque/internal/tag" - "github.com/bytemare/opaque/message" -) - -var ( - errInvalidMessageLength = errors.New("invalid message length for the configuration") - errInvalidStateLength = errors.New("invalid state length") - errStateExists = errors.New("existing state is not empty") -) - -func TestDeserializeRegistrationRequest(t *testing.T) { - c := opaque.DefaultConfiguration() - - server, _ := c.Server() - conf := server.GetConf() - length := conf.OPRFPointLength + 1 - if _, err := server.Deserialize.RegistrationRequest(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } - - client, _ := c.Client() - if _, err := client.Deserialize.RegistrationRequest(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } -} - -func TestDeserializeRegistrationResponse(t *testing.T) { - c := opaque.DefaultConfiguration() - - server, _ := c.Server() - conf := server.GetConf() - length := conf.OPRFPointLength + conf.AkePointLength + 1 - if _, err := server.Deserialize.RegistrationResponse(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } - - client, _ := c.Client() - if _, err := client.Deserialize.RegistrationResponse(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } -} - -func TestDeserializeRegistrationRecord(t *testing.T) { - for _, e := range confs { - server, _ := e.Conf.Server() - conf := server.GetConf() - length := conf.AkePointLength + conf.Hash.Size() + conf.EnvelopeSize + 1 - if _, err := server.Deserialize.RegistrationRecord(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } - - badPKu := getBadElement(t, e) - rec := encoding.Concat(badPKu, internal.RandomBytes(conf.Hash.Size()+conf.EnvelopeSize)) - - expect := "invalid client public key" - if _, err := server.Deserialize.RegistrationRecord(rec); err == nil || err.Error() != expect { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", expect, err) - } - - client, _ := e.Conf.Client() - if _, err := client.Deserialize.RegistrationRecord(internal.RandomBytes(length)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeRegistrationRequest. want %q, got %q", errInvalidMessageLength, err) - } - } -} - -func TestDeserializeKE1(t *testing.T) { - c := opaque.DefaultConfiguration() - g := group.Group(c.AKE) - ke1Length := encoding.PointLength[g] + internal.NonceLength + encoding.PointLength[g] - - server, _ := c.Server() - if _, err := server.Deserialize.KE1(internal.RandomBytes(ke1Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } - - client, _ := c.Client() - if _, err := client.Deserialize.KE1(internal.RandomBytes(ke1Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } -} - -func TestDeserializeKE2(t *testing.T) { - c := opaque.DefaultConfiguration() - - client, _ := c.Client() - conf := client.GetConf() - ke2Length := conf.OPRFPointLength + 2*conf.NonceLen + 2*conf.AkePointLength + conf.EnvelopeSize + conf.MAC.Size() - if _, err := client.Deserialize.KE2(internal.RandomBytes(ke2Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } - - server, _ := c.Server() - conf = server.GetConf() - ke2Length = conf.OPRFPointLength + 2*conf.NonceLen + 2*conf.AkePointLength + conf.EnvelopeSize + conf.MAC.Size() - if _, err := server.Deserialize.KE2(internal.RandomBytes(ke2Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } -} - -func TestDeserializeKE3(t *testing.T) { - c := opaque.DefaultConfiguration() - ke3Length := c.MAC.Size() - - server, _ := c.Server() - if _, err := server.Deserialize.KE3(internal.RandomBytes(ke3Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } - - client, _ := c.Client() - if _, err := client.Deserialize.KE3(internal.RandomBytes(ke3Length + 1)); err == nil || - err.Error() != errInvalidMessageLength.Error() { - t.Fatalf("Expected error for DeserializeKE1. want %q, got %q", errInvalidMessageLength, err) - } -} - -// opaque.go - -func TestDeserializeConfiguration_Short(t *testing.T) { - r9 := internal.RandomBytes(7) - - if _, err := opaque.DeserializeConfiguration(r9); !errors.Is(err, internal.ErrConfigurationInvalidLength) { - t.Errorf("DeserializeConfiguration did not return the appropriate error for vector r9. want %q, got %q", - internal.ErrConfigurationInvalidLength, err) - } -} - -func TestDeserializeConfiguration_InvalidContextHeader(t *testing.T) { - d := opaque.DefaultConfiguration().Serialize() - d[7] = 3 - - expected := "decoding the configuration context: " - if _, err := opaque.DeserializeConfiguration(d); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Errorf( - "DeserializeConfiguration did not return the appropriate error for vector invalid header. want %q, got %q", - expected, - err, - ) - } -} - -func TestNilConfiguration(t *testing.T) { - def := opaque.DefaultConfiguration() - g := group.Group(def.AKE) - defaultConfiguration := &internal.Configuration{ - KDF: internal.NewKDF(def.KDF), - MAC: internal.NewMac(def.MAC), - Hash: internal.NewHash(def.Hash), - KSF: internal.NewKSF(def.KSF), - NonceLen: internal.NonceLength, - OPRFPointLength: encoding.PointLength[g], - AkePointLength: encoding.PointLength[g], - Group: g, - OPRF: oprf.Ciphersuite(g), - Context: def.Context, - } - - s, _ := opaque.NewServer(nil) - if reflect.DeepEqual(s.GetConf(), defaultConfiguration) { - t.Errorf("server did not default to correct configuration") - } - - c, _ := opaque.NewClient(nil) - if reflect.DeepEqual(c.GetConf(), defaultConfiguration) { - t.Errorf("client did not default to correct configuration") - } -} - -// helper functions - -type configuration struct { - Conf *opaque.Configuration - Curve elliptic.Curve -} - -var confs = []configuration{ - { - Conf: opaque.DefaultConfiguration(), - Curve: nil, - }, - { - Conf: &opaque.Configuration{ - OPRF: opaque.P256Sha256, - KDF: crypto.SHA256, - MAC: crypto.SHA256, - Hash: crypto.SHA256, - KSF: ksf.Scrypt, - AKE: opaque.P256Sha256, - }, - Curve: elliptic.P256(), - }, - { - Conf: &opaque.Configuration{ - OPRF: opaque.P384Sha512, - KDF: crypto.SHA512, - MAC: crypto.SHA512, - Hash: crypto.SHA512, - KSF: ksf.Scrypt, - AKE: opaque.P384Sha512, - }, - Curve: elliptic.P384(), - }, - { - Conf: &opaque.Configuration{ - OPRF: opaque.P521Sha512, - KDF: crypto.SHA512, - MAC: crypto.SHA512, - Hash: crypto.SHA512, - KSF: ksf.Scrypt, - AKE: opaque.P521Sha512, - }, - Curve: elliptic.P521(), - }, - //{ - // Conf: &opaque.Configuration{ - // OPRF: opaque.RistrettoSha512, - // KDF: crypto.SHA512, - // MAC: crypto.SHA512, - // Hash: crypto.SHA512, - // KSF: ksf.Scrypt, - // Mode: opaque.Internal, - // AKE: opaque.Curve25519Sha512, - // }, - // Curve: nil, - //}, -} - -func getBadRistrettoScalar() []byte { - a := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - decoded, _ := hex.DecodeString(a) - - return decoded -} - -func getBadRistrettoElement() []byte { - a := "2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08" - decoded, _ := hex.DecodeString(a) - - return decoded -} - -func getBad25519Element() []byte { - a := "efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f" - decoded, _ := hex.DecodeString(a) - - return decoded -} - -func getBad25519Scalar() []byte { - a := "ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000011" - decoded, _ := hex.DecodeString(a) - - return decoded -} - -func badScalar(t *testing.T, g group.Group, curve elliptic.Curve) []byte { - order := curve.Params().P - exceeded := new(big.Int).Add(order, big.NewInt(2)).Bytes() - - _, err := g.NewScalar().Decode(exceeded) - if err == nil { - t.Errorf("Exceeding order did not yield an error for group %s", g) - } - - return exceeded -} - -func getBadNistElement(t *testing.T, id group.Group) []byte { - size := encoding.PointLength[id] - element := internal.RandomBytes(size) - // detag compression - element[0] = 4 - - // test if invalid compression is detected - _, err := id.NewElement().Decode(element) - if err == nil { - t.Errorf("detagged compressed point did not yield an error for group %s", id) - } - - return element -} - -func getBadElement(t *testing.T, c configuration) []byte { - switch c.Conf.AKE { - case opaque.RistrettoSha512: - return getBadRistrettoElement() - // case opaque.Curve25519Sha512: - // return getBad25519Element() - default: - return getBadNistElement(t, group.Group(c.Conf.AKE)) - } -} - -func getBadScalar(t *testing.T, c configuration) []byte { - switch c.Conf.AKE { - case opaque.RistrettoSha512: - return getBadRistrettoScalar() - // case opaque.Curve25519Sha512: - // return getBad25519Scalar() - default: - return badScalar(t, oprf.Ciphersuite(c.Conf.AKE).Group(), c.Curve) - } -} - -func buildRecord( - credID, oprfSeed, password, pks []byte, - client *opaque.Client, - server *opaque.Server, -) *opaque.ClientRecord { - conf := server.GetConf() - r1 := client.RegistrationInit(password) - pk, err := conf.Group.NewElement().Decode(pks) - if err != nil { - panic(err) - } - r2 := server.RegistrationResponse(r1, pk, credID, oprfSeed) - r3, _ := client.RegistrationFinalize(r2, nil, nil) - - return &opaque.ClientRecord{ - CredentialIdentifier: credID, - ClientIdentity: nil, - RegistrationRecord: r3, - TestMaskNonce: nil, - } -} - -func buildPRK(client *opaque.Client, evaluation *group.Point) ([]byte, error) { - conf := client.GetConf() - unblinded := client.OPRF.Finalize(evaluation) - hardened := conf.KSF.Harden(unblinded, nil, conf.OPRFPointLength) - - return conf.KDF.Extract(nil, hardened), nil -} - -func getEnvelope(client *opaque.Client, ke2 *message.KE2) (*keyrecovery.Envelope, []byte, error) { - conf := client.GetConf() - randomizedPwd, err := buildPRK(client, ke2.EvaluatedMessage) - if err != nil { - return nil, nil, fmt.Errorf("finalizing OPRF : %w", err) - } - - maskingKey := conf.KDF.Expand(randomizedPwd, []byte(tag.MaskingKey), conf.Hash.Size()) - - clear := conf.XorResponse(maskingKey, ke2.MaskingNonce, ke2.MaskedResponse) - e := clear[encoding.PointLength[conf.Group]:] - - env := &keyrecovery.Envelope{ - Nonce: e[:conf.NonceLen], - AuthTag: e[conf.NonceLen:], - } - - return env, randomizedPwd, nil -} - -// server.go - -func TestServer_BadRegistrationRequest(t *testing.T) { - /* - Error in OPRF - - client blinded element invalid point encoding - */ - err1 := "invalid message length" - err2 := "blinded data is an invalid point" - - for i, e := range confs { - server, _ := e.Conf.Server() - if _, err := server.Deserialize.RegistrationRequest(nil); err == nil || !strings.HasPrefix(err.Error(), err1) { - t.Fatalf("#%d - expected error. Got %v", i, err) - } - - bad := getBadElement(t, e) - if _, err := server.Deserialize.RegistrationRequest(bad); err == nil || !strings.HasPrefix(err.Error(), err2) { - t.Fatalf("#%d - expected error. Got %v", i, err) - } - } -} - -func TestServerInit_InvalidPublicKey(t *testing.T) { - /* - Nil and invalid server public key - */ - for _, conf := range confs { - server, _ := conf.Conf.Server() - sk, _ := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - - expected := "input server public key's length is invalid" - if _, err := server.LoginInit(nil, nil, sk, nil, oprfSeed, nil); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on nil pubkey - got %s", err) - } - - expected = "invalid server public key: " - if _, err := server.LoginInit(nil, nil, sk, getBadElement(t, conf), oprfSeed, nil); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on bad secret key - got %s", err) - } - } -} - -func TestServerInit_InvalidOPRFSeedLength(t *testing.T) { - /* - Nil and invalid server public key - */ - for _, conf := range confs { - server, _ := conf.Conf.Server() - sk, pk := conf.Conf.KeyGen() - expected := opaque.ErrInvalidOPRFSeedLength - - if _, err := server.LoginInit(nil, nil, sk, pk, nil, nil); err == nil || !errors.Is(err, expected) { - t.Fatalf("expected error on nil seed - got %s", err) - } - - seed := internal.RandomBytes(conf.Conf.Hash.Size() - 1) - if _, err := server.LoginInit(nil, nil, sk, pk, seed, nil); err == nil || !errors.Is(err, expected) { - t.Fatalf("expected error on bad seed - got %s", err) - } - - seed = internal.RandomBytes(conf.Conf.Hash.Size() + 1) - if _, err := server.LoginInit(nil, nil, sk, pk, seed, nil); err == nil || !errors.Is(err, expected) { - t.Fatalf("expected error on bad seed - got %s", err) - } - } -} - -func TestServerInit_NilSecretKey(t *testing.T) { - /* - Nil server secret key - */ - for _, conf := range confs { - server, _ := conf.Conf.Server() - _, pk := conf.Conf.KeyGen() - expected := "invalid server secret key: " - - if _, err := server.LoginInit(nil, nil, nil, pk, nil, nil); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on nil secret key - got %s", err) - } - } -} - -func TestServerInit_InvalidEnvelope(t *testing.T) { - /* - Record envelope of invalid length - */ - for _, conf := range confs { - server, _ := conf.Conf.Server() - sk, pk := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - client, _ := conf.Conf.Client() - rec := buildRecord(internal.RandomBytes(32), oprfSeed, []byte("yo"), pk, client, server) - rec.Envelope = internal.RandomBytes(15) - - expected := "record has invalid envelope length" - if _, err := server.LoginInit(nil, nil, sk, pk, oprfSeed, rec); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on nil secret key - got %s", err) - } - } -} - -func TestServerInit_InvalidData(t *testing.T) { - /* - Invalid OPRF data in KE1 - */ - for _, conf := range confs { - server, _ := conf.Conf.Server() - ke1 := encoding.Concatenate( - getBadElement(t, conf), - internal.RandomBytes(server.GetConf().NonceLen), - internal.RandomBytes(server.GetConf().AkePointLength), - ) - expected := "blinded data is an invalid point" - if _, err := server.Deserialize.KE1(ke1); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on bad oprf request - got %s", err) - } - } -} - -func TestServerInit_InvalidEPKU(t *testing.T) { - /* - Invalid EPKU in KE1 - */ - rec := &opaque.ClientRecord{ - CredentialIdentifier: internal.RandomBytes(32), - ClientIdentity: nil, - RegistrationRecord: &message.RegistrationRecord{ - MaskingKey: internal.RandomBytes(32), - }, - TestMaskNonce: nil, - } - - for _, conf := range confs { - rec.Envelope = opaque.GetFakeEnvelope(conf.Conf) - server, _ := conf.Conf.Server() - client, _ := conf.Conf.Client() - ke1 := client.LoginInit([]byte("yo")).Serialize() - badke1 := encoding.Concat( - ke1[:server.GetConf().OPRFPointLength+server.GetConf().NonceLen], - getBadElement(t, conf), - ) - expected := "invalid ephemeral client public key" - if _, err := server.Deserialize.KE1(badke1); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on bad epku - got %s", err) - } - } -} - -func TestServerFinish_InvalidKE3Mac(t *testing.T) { - /* - ke3 mac is invalid - */ - conf := opaque.DefaultConfiguration() - credId := internal.RandomBytes(32) - oprfSeed := internal.RandomBytes(conf.Hash.Size()) - client, _ := conf.Client() - server, _ := conf.Server() - sk, pk := conf.KeyGen() - rec := buildRecord(credId, oprfSeed, []byte("yo"), pk, client, server) - ke1 := client.LoginInit([]byte("yo")) - ke2, err := server.LoginInit(ke1, nil, sk, pk, oprfSeed, rec) - if err != nil { - t.Fatal(err) - } - ke3, _, err := client.LoginFinish(nil, nil, ke2) - if err != nil { - t.Fatal(err) - } - ke3.Mac[0] = ^ke3.Mac[0] - - expected := opaque.ErrAkeInvalidClientMac - if err := server.LoginFinish(ke3); err == nil || err.Error() != expected.Error() { - t.Fatalf("expected error on invalid mac - got %v", err) - } -} - -func TestServerSetAKEState_InvalidInput(t *testing.T) { - conf := opaque.DefaultConfiguration() - - /* - Test an invalid state - */ - - buf := internal.RandomBytes(conf.MAC.Size() + conf.KDF.Size() + 1) - - server, _ := conf.Server() - if err := server.SetAKEState(buf); err == nil || err.Error() != errInvalidStateLength.Error() { - t.Fatalf("Expected error for SetAKEState. want %q, got %q", errInvalidStateLength, err) - } - - /* - A state already exists. - */ - - credId := internal.RandomBytes(32) - seed := internal.RandomBytes(conf.Hash.Size()) - client, _ := conf.Client() - server, _ = conf.Server() - sk, pk := conf.KeyGen() - rec := buildRecord(credId, seed, []byte("yo"), pk, client, server) - ke1 := client.LoginInit([]byte("yo")) - _, _ = server.LoginInit(ke1, nil, sk, pk, seed, rec) - state := server.SerializeState() - if err := server.SetAKEState(state); err == nil || err.Error() != errStateExists.Error() { - t.Fatalf("Expected error for SetAKEState. want %q, got %q", errStateExists, err) - } -} - -// client.go -func TestClientRegistrationFinalize_InvalidPks(t *testing.T) { - /* - Invalid data sent to the client - */ - credID := internal.RandomBytes(32) - - for _, conf := range confs { - client, _ := conf.Conf.Client() - server, _ := conf.Conf.Server() - _, pks := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - r1 := client.RegistrationInit([]byte("yo")) - - pk, err := server.GetConf().Group.NewElement().Decode(pks) - if err != nil { - panic(err) - } - r2 := server.RegistrationResponse(r1, pk, credID, oprfSeed) - - // message length - badr2 := internal.RandomBytes(15) - expected := "invalid message length" - if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for empty server public key - got %v", err) - } - - // invalid data - badr2 = encoding.Concat(getBadElement(t, conf), pks) - expected = "invalid OPRF evaluation" - if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for empty server public key - got %v", err) - } - - // nil pks - expected = "invalid server public key" - badr2 = encoding.Concat(r2.Serialize()[:client.GetConf().OPRFPointLength], getBadElement(t, conf)) - if _, err := client.Deserialize.RegistrationResponse(badr2); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid server public key - got %v", err) - } - } -} - -func TestClientFinish_BadEvaluation(t *testing.T) { - /* - Oprf finalize : evaluation deserialization // element decoding - */ - for _, conf := range confs { - client, _ := conf.Conf.Client() - _ = client.LoginInit([]byte("yo")) - r2 := encoding.Concat( - getBadElement(t, conf), - internal.RandomBytes( - client.GetConf().NonceLen+client.GetConf().AkePointLength+client.GetConf().EnvelopeSize, - ), - ) - badKe2 := encoding.Concat( - r2, - internal.RandomBytes(client.GetConf().NonceLen+client.GetConf().AkePointLength+client.GetConf().MAC.Size()), - ) - - expected := "invalid OPRF evaluation" - if _, err := client.Deserialize.KE2(badKe2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid evaluated element - got %v", err) - } - } -} - -func TestClientFinish_BadMaskedResponse(t *testing.T) { - /* - The masked response is of invalid length. - */ - credID := internal.RandomBytes(32) - - for _, conf := range confs { - client, _ := conf.Conf.Client() - server, _ := conf.Conf.Server() - sks, pks := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) - - ke1 := client.LoginInit([]byte("yo")) - ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) - - goodLength := encoding.PointLength[client.GetConf().Group] + client.GetConf().EnvelopeSize - expected := "invalid masked response length" - - // too short - ke2.MaskedResponse = internal.RandomBytes(goodLength - 1) - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for short response - got %v", err) - } - - // too long - ke2.MaskedResponse = internal.RandomBytes(goodLength + 1) - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for long response - got %v", err) - } - } -} - -func TestClientFinish_InvalidEnvelopeTag(t *testing.T) { - /* - Invalid envelope tag - */ - credID := internal.RandomBytes(32) - - for _, conf := range confs { - client, _ := conf.Conf.Client() - server, _ := conf.Conf.Server() - sks, pks := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) - - ke1 := client.LoginInit([]byte("yo")) - ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) - - env, _, err := getEnvelope(client, ke2) - if err != nil { - t.Fatal(err) - } - - // tamper the envelope - env.AuthTag = internal.RandomBytes(client.GetConf().MAC.Size()) - clear := encoding.Concat(pks, env.Serialize()) - ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) - - // too short - expected := "recover envelope: invalid envelope authentication tag" - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid envelope mac - got %v", err) - } - } -} - -func cleartextCredentials(clientPublicKey, serverPublicKey, idc, ids []byte) []byte { - if ids == nil { - ids = serverPublicKey - } - - if idc == nil { - idc = clientPublicKey - } - - return encoding.Concat3(serverPublicKey, encoding.EncodeVector(ids), encoding.EncodeVector(idc)) -} - -func TestClientFinish_InvalidKE2KeyEncoding(t *testing.T) { - /* - Tamper KE2 values - */ - credID := internal.RandomBytes(32) - - for _, conf := range confs { - client, _ := conf.Conf.Client() - server, _ := conf.Conf.Server() - sks, pks := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) - - ke1 := client.LoginInit([]byte("yo")) - ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) - // epks := ke2.EpkS - - // tamper epks - offset := client.GetConf().AkePointLength + client.GetConf().MAC.Size() - encoded := ke2.Serialize() - badKe2 := encoding.Concat3(encoded[:len(encoded)-offset], getBadElement(t, conf), ke2.Mac) - expected := "invalid ephemeral server public key" - if _, err := client.Deserialize.KE2(badKe2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid epks encoding - got %q", err) - } - - // tamper PKS - // ke2.EpkS = server.Group.NewElement().Mult(server.Group.NewScalar().Random()) - env, randomizedPwd, err := getEnvelope(client, ke2) - if err != nil { - t.Fatal(err) - } - - badpks := getBadElement(t, conf) - - ctc := cleartextCredentials( - encoding.SerializePoint(rec.RegistrationRecord.PublicKey, client.GetConf().Group), - badpks, - nil, - nil, - ) - authKey := client.GetConf().KDF.Expand( - randomizedPwd, - encoding.SuffixString(env.Nonce, tag.AuthKey), - client.GetConf().KDF.Size(), - ) - authTag := client.GetConf().MAC.MAC(authKey, encoding.Concat(env.Nonce, ctc)) - env.AuthTag = authTag - - clear := encoding.Concat(badpks, env.Serialize()) - ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) - - expected = "invalid server public key" - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid envelope mac - got %q", err) - } - - // replace PKS - fakepks := server.GetConf().Group.Base().Mult(server.GetConf().Group.NewScalar().Random()).Bytes() - clear = encoding.Concat(fakepks, env.Serialize()) - ke2.MaskedResponse = server.GetConf().XorResponse(rec.MaskingKey, ke2.MaskingNonce, clear) - - expected = "recover envelope: invalid envelope authentication tag" - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid envelope mac - got %q", err) - } - } -} - -func TestClientFinish_InvalidKE2Mac(t *testing.T) { - /* - Invalid server ke2 mac - */ - credID := internal.RandomBytes(32) - - for _, conf := range confs { - client, _ := conf.Conf.Client() - server, _ := conf.Conf.Server() - sks, pks := conf.Conf.KeyGen() - oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) - rec := buildRecord(credID, oprfSeed, []byte("yo"), pks, client, server) - - ke1 := client.LoginInit([]byte("yo")) - ke2, _ := server.LoginInit(ke1, nil, sks, pks, oprfSeed, rec) - - ke2.Mac = internal.RandomBytes(client.GetConf().MAC.Size()) - expected := " AKE finalization: invalid server mac" - if _, _, err := client.LoginFinish(nil, nil, ke2); err == nil || !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error for invalid epks encoding - got %q", err) - } - } -} diff --git a/tests/helper_test.go b/tests/helper_test.go new file mode 100644 index 0000000..ae64b2a --- /dev/null +++ b/tests/helper_test.go @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package opaque_test + +import ( + "crypto" + "crypto/elliptic" + "encoding/hex" + "fmt" + "math/big" + "testing" + + "github.com/bytemare/crypto/group" + "github.com/bytemare/crypto/ksf" + + "github.com/bytemare/opaque" + "github.com/bytemare/opaque/internal" + "github.com/bytemare/opaque/internal/encoding" + "github.com/bytemare/opaque/internal/keyrecovery" + "github.com/bytemare/opaque/internal/oprf" + "github.com/bytemare/opaque/internal/tag" + "github.com/bytemare/opaque/message" +) + +// helper functions + +type configuration struct { + Conf *opaque.Configuration + Curve elliptic.Curve +} + +var confs = []configuration{ + { + Conf: opaque.DefaultConfiguration(), + Curve: nil, + }, + { + Conf: &opaque.Configuration{ + OPRF: opaque.P256Sha256, + KDF: crypto.SHA256, + MAC: crypto.SHA256, + Hash: crypto.SHA256, + KSF: ksf.Scrypt, + AKE: opaque.P256Sha256, + }, + Curve: elliptic.P256(), + }, + { + Conf: &opaque.Configuration{ + OPRF: opaque.P384Sha512, + KDF: crypto.SHA512, + MAC: crypto.SHA512, + Hash: crypto.SHA512, + KSF: ksf.Scrypt, + AKE: opaque.P384Sha512, + }, + Curve: elliptic.P384(), + }, + { + Conf: &opaque.Configuration{ + OPRF: opaque.P521Sha512, + KDF: crypto.SHA512, + MAC: crypto.SHA512, + Hash: crypto.SHA512, + KSF: ksf.Scrypt, + AKE: opaque.P521Sha512, + }, + Curve: elliptic.P521(), + }, + //{ + // Conf: &opaque.Configuration{ + // OPRF: opaque.RistrettoSha512, + // KDF: crypto.SHA512, + // MAC: crypto.SHA512, + // Hash: crypto.SHA512, + // KSF: ksf.Scrypt, + // Mode: opaque.Internal, + // AKE: opaque.Curve25519Sha512, + // }, + // Curve: nil, + //}, +} + +func getBadRistrettoScalar() []byte { + a := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func getBadRistrettoElement() []byte { + a := "2a292df7e32cababbd9de088d1d1abec9fc0440f637ed2fba145094dc14bea08" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func getBad25519Element() []byte { + a := "efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func getBad25519Scalar() []byte { + a := "ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000011" + decoded, _ := hex.DecodeString(a) + + return decoded +} + +func badScalar(t *testing.T, g group.Group, curve elliptic.Curve) []byte { + order := curve.Params().P + exceeded := new(big.Int).Add(order, big.NewInt(2)).Bytes() + + _, err := g.NewScalar().Decode(exceeded) + if err == nil { + t.Errorf("Exceeding order did not yield an error for group %s", g) + } + + return exceeded +} + +func getBadNistElement(t *testing.T, id group.Group) []byte { + size := encoding.PointLength[id] + element := internal.RandomBytes(size) + // detag compression + element[0] = 4 + + // test if invalid compression is detected + _, err := id.NewElement().Decode(element) + if err == nil { + t.Errorf("detagged compressed point did not yield an error for group %s", id) + } + + return element +} + +func getBadElement(t *testing.T, c configuration) []byte { + switch c.Conf.AKE { + case opaque.RistrettoSha512: + return getBadRistrettoElement() + // case opaque.Curve25519Sha512: + // return getBad25519Element() + default: + return getBadNistElement(t, group.Group(c.Conf.AKE)) + } +} + +func getBadScalar(t *testing.T, c configuration) []byte { + switch c.Conf.AKE { + case opaque.RistrettoSha512: + return getBadRistrettoScalar() + // case opaque.Curve25519Sha512: + // return getBad25519Scalar() + default: + return badScalar(t, oprf.Ciphersuite(c.Conf.AKE).Group(), c.Curve) + } +} + +func buildRecord( + credID, oprfSeed, password, pks []byte, + client *opaque.Client, + server *opaque.Server, +) *opaque.ClientRecord { + conf := server.GetConf() + r1 := client.RegistrationInit(password) + pk, err := conf.Group.NewElement().Decode(pks) + if err != nil { + panic(err) + } + r2 := server.RegistrationResponse(r1, pk, credID, oprfSeed) + r3, _ := client.RegistrationFinalize(r2, nil, nil) + + return &opaque.ClientRecord{ + CredentialIdentifier: credID, + ClientIdentity: nil, + RegistrationRecord: r3, + TestMaskNonce: nil, + } +} + +func buildPRK(client *opaque.Client, evaluation *group.Point) ([]byte, error) { + conf := client.GetConf() + unblinded := client.OPRF.Finalize(evaluation) + hardened := conf.KSF.Harden(unblinded, nil, conf.OPRFPointLength) + + return conf.KDF.Extract(nil, hardened), nil +} + +func getEnvelope(client *opaque.Client, ke2 *message.KE2) (*keyrecovery.Envelope, []byte, error) { + conf := client.GetConf() + randomizedPwd, err := buildPRK(client, ke2.EvaluatedMessage) + if err != nil { + return nil, nil, fmt.Errorf("finalizing OPRF : %w", err) + } + + maskingKey := conf.KDF.Expand(randomizedPwd, []byte(tag.MaskingKey), conf.Hash.Size()) + + clear := conf.XorResponse(maskingKey, ke2.MaskingNonce, ke2.MaskedResponse) + e := clear[encoding.PointLength[conf.Group]:] + + env := &keyrecovery.Envelope{ + Nonce: e[:conf.NonceLen], + AuthTag: e[conf.NonceLen:], + } + + return env, randomizedPwd, nil +} diff --git a/tests/i2osp_test.go b/tests/i2osp_test.go deleted file mode 100644 index c8e04a5..0000000 --- a/tests/i2osp_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree or at -// https://spdx.org/licenses/MIT.html - -package opaque_test - -import ( - "bytes" - "encoding/hex" - "fmt" - "testing" - - "github.com/bytemare/opaque/internal/encoding" -) - -type I2OSPTest struct { - value int - size int - encoded []byte -} - -var I2OSPVectors = []I2OSPTest{ - { - 0, 1, []byte{0}, - }, - { - 1, 1, []byte{1}, - }, - { - 255, 1, []byte{0xff}, - }, - { - 256, 2, []byte{0x01, 0x00}, - }, - { - 65535, 2, []byte{0xff, 0xff}, - }, - { - 16770000, 3, []byte{0xff, 0xe3, 0xd0}, - }, - { - 4294960000, 4, []byte{0xff, 0xff, 0xe3, 0x80}, - }, -} - -func TestI2OSP(t *testing.T) { - for i, v := range I2OSPVectors { - t.Run(fmt.Sprintf("%d - %d - %v", v.value, v.size, v.encoded), func(t *testing.T) { - r := encoding.I2OSP(v.value, v.size) - - if !bytes.Equal(r, v.encoded) { - t.Fatalf( - "invalid encoding for %d. Expected '%s', got '%v'", - i, - hex.EncodeToString(v.encoded), - hex.EncodeToString(r), - ) - } - - value := encoding.OS2IP(v.encoded) - if v.value != value { - t.Errorf("invalid decoding for %d. Expected %d, got %d", i, v.value, value) - } - }) - } - - length := -1 - if hasPanic, err := expectPanic(nil, func() { - _ = encoding.I2OSP(1, length) - }); !hasPanic { - t.Fatalf("expected panic with with negative length: %v", err) - } - - length = 0 - if hasPanic, err := expectPanic(nil, func() { - _ = encoding.I2OSP(1, length) - }); !hasPanic { - t.Fatalf("expected panic with with 0 length: %v", err) - } - - length = 5 - if hasPanic, err := expectPanic(nil, func() { - _ = encoding.I2OSP(1, length) - }); !hasPanic { - t.Fatalf("expected panic with length too big: %v", err) - } - - negative := -1 - if hasPanic, err := expectPanic(nil, func() { - _ = encoding.I2OSP(negative, 4) - }); !hasPanic { - t.Fatalf("expected panic with negative input: %v", err) - } - - tooLarge := 1 << 32 - length = 1 - if hasPanic, err := expectPanic(nil, func() { - _ = encoding.I2OSP(tooLarge, length) - }); !hasPanic { - t.Fatalf("expected panic with exceeding value for the length: %v", err) - } - - lengths := map[int]int{ - 100: 1, - 1 << 8: 2, - 1 << 16: 3, - (1 << 32) - 1: 4, - } - - for k, v := range lengths { - r := encoding.I2OSP(k, v) - - if len(r) != v { - t.Fatalf("invalid length for %d. Expected '%d', got '%d' (%v)", k, v, len(r), r) - } - } -} - -func TestOS2IP(t *testing.T) { - // No input - if hasPanic, _ := expectPanic(nil, func() { - _ = encoding.OS2IP(nil) - }); !hasPanic { - t.Fatal("expected panic with nil input") - } - - // Empty input - if hasPanic, _ := expectPanic(nil, func() { - _ = encoding.OS2IP([]byte("")) - }); !hasPanic { - t.Fatal("expected panic with empty input") - } - - // Exceeding input - input := "12345" - if hasPanic, _ := expectPanic(nil, func() { - _ = encoding.OS2IP([]byte(input)) - }); !hasPanic { - t.Fatal("expected panic with big input") - } -} - -func hasPanic(f func()) (has bool, err error) { - err = nil - var report interface{} - func() { - defer func() { - if report = recover(); report != nil { - has = true - } - }() - - f() - }() - - if has { - err = fmt.Errorf("%v", report) - } - - return -} - -func expectPanic(expectedError error, f func()) (bool, string) { - hasPanic, err := hasPanic(f) - - if !hasPanic { - return false, "no panic" - } - - if expectedError == nil { - return true, "" - } - - if err == nil { - return false, "panic but no message" - } - - if err.Error() != expectedError.Error() { - return false, fmt.Sprintf("expected %q, got %q", expectedError, err) - } - - return true, "" -} diff --git a/tests/opaque_test.go b/tests/opaque_test.go index 579b025..0af66f5 100644 --- a/tests/opaque_test.go +++ b/tests/opaque_test.go @@ -10,10 +10,19 @@ package opaque_test import ( "bytes" + "crypto" + "errors" + "reflect" + "strings" "testing" + "github.com/bytemare/crypto/group" + "github.com/bytemare/crypto/ksf" + "github.com/bytemare/opaque" "github.com/bytemare/opaque/internal" + "github.com/bytemare/opaque/internal/encoding" + "github.com/bytemare/opaque/internal/oprf" ) const dbgErr = "%v" @@ -198,3 +207,161 @@ func testAuthentication(t *testing.T, p *testParams, record *opaque.ClientRecord return exportKeyLogin } + +/* + The following tests look for failing conditions. +*/ + +func TestNilConfiguration(t *testing.T) { + def := opaque.DefaultConfiguration() + g := group.Group(def.AKE) + defaultConfiguration := &internal.Configuration{ + KDF: internal.NewKDF(def.KDF), + MAC: internal.NewMac(def.MAC), + Hash: internal.NewHash(def.Hash), + KSF: internal.NewKSF(def.KSF), + NonceLen: internal.NonceLength, + OPRFPointLength: encoding.PointLength[g], + AkePointLength: encoding.PointLength[g], + Group: g, + OPRF: oprf.Ciphersuite(g), + Context: def.Context, + } + + s, _ := opaque.NewServer(nil) + if reflect.DeepEqual(s.GetConf(), defaultConfiguration) { + t.Errorf("server did not default to correct configuration") + } + + c, _ := opaque.NewClient(nil) + if reflect.DeepEqual(c.GetConf(), defaultConfiguration) { + t.Errorf("client did not default to correct configuration") + } +} + +func TestDeserializeConfiguration_Short(t *testing.T) { + r9 := internal.RandomBytes(7) + + if _, err := opaque.DeserializeConfiguration(r9); !errors.Is(err, internal.ErrConfigurationInvalidLength) { + t.Errorf("DeserializeConfiguration did not return the appropriate error for vector r9. want %q, got %q", + internal.ErrConfigurationInvalidLength, err) + } +} + +func TestDeserializeConfiguration_InvalidContextHeader(t *testing.T) { + d := opaque.DefaultConfiguration().Serialize() + d[7] = 3 + + expected := "decoding the configuration context: " + if _, err := opaque.DeserializeConfiguration(d); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Errorf( + "DeserializeConfiguration did not return the appropriate error for vector invalid header. want %q, got %q", + expected, + err, + ) + } +} + +func TestBadConfiguration(t *testing.T) { + setBadValue := func(pos, val int) []byte { + b := opaque.DefaultConfiguration().Serialize() + b[pos] = byte(val) + return b + } + + tests := []struct { + name string + makeBad func() []byte + error string + }{ + { + name: "Bad OPRF", + makeBad: func() []byte { + return setBadValue(0, 0) + }, + error: "invalid OPRF group id", + }, + { + name: "Bad KDF", + makeBad: func() []byte { + return setBadValue(1, 0) + }, + error: "invalid KDF id", + }, + { + name: "Bad MAC", + makeBad: func() []byte { + return setBadValue(2, 0) + }, + error: "invalid MAC id", + }, + { + name: "Bad Hash", + makeBad: func() []byte { + return setBadValue(3, 0) + }, + error: "invalid Hash id", + }, + { + name: "Bad KSF", + makeBad: func() []byte { + return setBadValue(4, 10) + }, + error: "invalid KSF id", + }, + { + name: "Bad AKE", + makeBad: func() []byte { + return setBadValue(5, 0) + }, + error: "invalid AKE group id", + }, + } + + convertToBadConf := func(encoded []byte) *opaque.Configuration { + return &opaque.Configuration{ + OPRF: opaque.Group(encoded[0]), + KDF: crypto.Hash(encoded[1]), + MAC: crypto.Hash(encoded[2]), + Hash: crypto.Hash(encoded[3]), + KSF: ksf.Identifier(encoded[4]), + AKE: opaque.Group(encoded[5]), + Context: encoded[5:], + } + } + + for _, badConf := range tests { + t.Run(badConf.name, func(t *testing.T) { + // Test Deserialization for bad conf + badEncoded := badConf.makeBad() + _, err := opaque.DeserializeConfiguration(badEncoded) + if err == nil || !strings.EqualFold(err.Error(), badConf.error) { + t.Fatalf( + "Expected error for %s. Want %q, got %q.\n\tEncoded: %v", + badConf.name, + badConf.error, + err, + badEncoded, + ) + } + + // Test bad configuration for client, server, and deserializer setup + bad := convertToBadConf(badEncoded) + + _, err = bad.Client() + if err == nil || !strings.EqualFold(err.Error(), badConf.error) { + t.Fatalf("Expected error for %s / client. Want %q, got %q", badConf.name, badConf.error, err) + } + + _, err = bad.Server() + if err == nil || !strings.EqualFold(err.Error(), badConf.error) { + t.Fatalf("Expected error for %s / server. Want %q, got %q", badConf.name, badConf.error, err) + } + + _, err = bad.Deserializer() + if err == nil || !strings.EqualFold(err.Error(), badConf.error) { + t.Fatalf("Expected error for %s / deserializer. Want %q, got %q", badConf.name, badConf.error, err) + } + }) + } +} diff --git a/tests/oprf_test.go b/tests/oprf_test.go index 8a62ccf..4d2678c 100644 --- a/tests/oprf_test.go +++ b/tests/oprf_test.go @@ -26,6 +26,11 @@ import ( "github.com/bytemare/opaque/internal/tag" ) +func TestOPRFString(t *testing.T) { + p := oprf.RistrettoSha512 + fmt.Println(p) +} + type oprfVector struct { DST string `json:"groupDST"` Hash string `json:"hash"` diff --git a/tests/server_test.go b/tests/server_test.go new file mode 100644 index 0000000..545a84e --- /dev/null +++ b/tests/server_test.go @@ -0,0 +1,256 @@ +package opaque_test + +import ( + "errors" + "log" + "strings" + "testing" + + H2C "github.com/armfazh/h2c-go-ref" + "github.com/bytemare/crypto/group" + + "github.com/bytemare/opaque" + "github.com/bytemare/opaque/internal" + "github.com/bytemare/opaque/internal/encoding" + "github.com/bytemare/opaque/message" +) + +var ( + errInvalidStateLength = errors.New("invalid state length") + errStateExists = errors.New("existing state is not empty") +) + +/* + The following tests look for failing conditions. +*/ + +func TestServer_BadRegistrationRequest(t *testing.T) { + /* + Error in OPRF + - client blinded element invalid point encoding + */ + err1 := "invalid message length" + err2 := "blinded data is an invalid point" + + for i, e := range confs { + server, _ := e.Conf.Server() + if _, err := server.Deserialize.RegistrationRequest(nil); err == nil || !strings.HasPrefix(err.Error(), err1) { + t.Fatalf("#%d - expected error. Got %v", i, err) + } + + bad := getBadElement(t, e) + if _, err := server.Deserialize.RegistrationRequest(bad); err == nil || !strings.HasPrefix(err.Error(), err2) { + t.Fatalf("#%d - expected error. Got %v", i, err) + } + } +} + +func TestIdentity(t *testing.T) { + g := group.P256Sha256 + log.Printf("Group: %v", g) + id := g.Identity() + log.Printf("Id: %v / %v / %v", id, id.Point.IsIdentity(), id.Point) + h, _ := H2C.P256_XMDSHA256_SSWU_RO_.Get(nil) + x := h.GetCurve().Identity().X() + y := h.GetCurve().Identity().Y() + log.Printf("\n%v\n%v", x, y) +} + +func TestServerInit_InvalidPublicKey(t *testing.T) { + /* + Nil and invalid server public key + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + sk, _ := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + + expected := "input server public key's length is invalid" + if _, err := server.LoginInit(nil, nil, sk, nil, oprfSeed, nil); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on nil pubkey - got %s", err) + } + + expected = "invalid server public key: " + if _, err := server.LoginInit(nil, nil, sk, getBadElement(t, conf), oprfSeed, nil); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on bad secret key - got %s", err) + } + + expected = "invalid server public key: pks is identity point" + log.Printf("id : %v", server.GetConf().Group) + identity := server.GetConf().Group.Identity().Bytes() + if _, err := server.LoginInit(nil, nil, sk, identity, oprfSeed, nil); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on bad secret key - got %s", err) + } + } +} + +func TestServerInit_InvalidOPRFSeedLength(t *testing.T) { + /* + Nil and invalid server public key + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + sk, pk := conf.Conf.KeyGen() + expected := opaque.ErrInvalidOPRFSeedLength + + if _, err := server.LoginInit(nil, nil, sk, pk, nil, nil); err == nil || !errors.Is(err, expected) { + t.Fatalf("expected error on nil seed - got %s", err) + } + + seed := internal.RandomBytes(conf.Conf.Hash.Size() - 1) + if _, err := server.LoginInit(nil, nil, sk, pk, seed, nil); err == nil || !errors.Is(err, expected) { + t.Fatalf("expected error on bad seed - got %s", err) + } + + seed = internal.RandomBytes(conf.Conf.Hash.Size() + 1) + if _, err := server.LoginInit(nil, nil, sk, pk, seed, nil); err == nil || !errors.Is(err, expected) { + t.Fatalf("expected error on bad seed - got %s", err) + } + } +} + +func TestServerInit_NilSecretKey(t *testing.T) { + /* + Nil server secret key + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + _, pk := conf.Conf.KeyGen() + expected := "invalid server secret key: " + + if _, err := server.LoginInit(nil, nil, nil, pk, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on nil secret key - got %s", err) + } + } +} + +func TestServerInit_InvalidEnvelope(t *testing.T) { + /* + Record envelope of invalid length + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + sk, pk := conf.Conf.KeyGen() + oprfSeed := internal.RandomBytes(conf.Conf.Hash.Size()) + client, _ := conf.Conf.Client() + rec := buildRecord(internal.RandomBytes(32), oprfSeed, []byte("yo"), pk, client, server) + rec.Envelope = internal.RandomBytes(15) + + expected := "record has invalid envelope length" + if _, err := server.LoginInit(nil, nil, sk, pk, oprfSeed, rec); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on nil secret key - got %s", err) + } + } +} + +func TestServerInit_InvalidData(t *testing.T) { + /* + Invalid OPRF data in KE1 + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + ke1 := encoding.Concatenate( + getBadElement(t, conf), + internal.RandomBytes(server.GetConf().NonceLen), + internal.RandomBytes(server.GetConf().AkePointLength), + ) + expected := "blinded data is an invalid point" + if _, err := server.Deserialize.KE1(ke1); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on bad oprf request - got %s", err) + } + } +} + +func TestServerInit_InvalidEPKU(t *testing.T) { + /* + Invalid EPKU in KE1 + */ + rec := &opaque.ClientRecord{ + CredentialIdentifier: internal.RandomBytes(32), + ClientIdentity: nil, + RegistrationRecord: &message.RegistrationRecord{ + MaskingKey: internal.RandomBytes(32), + }, + TestMaskNonce: nil, + } + + for _, conf := range confs { + rec.Envelope = opaque.GetFakeEnvelope(conf.Conf) + server, _ := conf.Conf.Server() + client, _ := conf.Conf.Client() + ke1 := client.LoginInit([]byte("yo")).Serialize() + badke1 := encoding.Concat( + ke1[:server.GetConf().OPRFPointLength+server.GetConf().NonceLen], + getBadElement(t, conf), + ) + expected := "invalid ephemeral client public key" + if _, err := server.Deserialize.KE1(badke1); err == nil || !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on bad epku - got %s", err) + } + } +} + +func TestServerFinish_InvalidKE3Mac(t *testing.T) { + /* + ke3 mac is invalid + */ + conf := opaque.DefaultConfiguration() + credId := internal.RandomBytes(32) + oprfSeed := internal.RandomBytes(conf.Hash.Size()) + client, _ := conf.Client() + server, _ := conf.Server() + sk, pk := conf.KeyGen() + rec := buildRecord(credId, oprfSeed, []byte("yo"), pk, client, server) + ke1 := client.LoginInit([]byte("yo")) + ke2, err := server.LoginInit(ke1, nil, sk, pk, oprfSeed, rec) + if err != nil { + t.Fatal(err) + } + ke3, _, err := client.LoginFinish(nil, nil, ke2) + if err != nil { + t.Fatal(err) + } + ke3.Mac[0] = ^ke3.Mac[0] + + expected := opaque.ErrAkeInvalidClientMac + if err := server.LoginFinish(ke3); err == nil || err.Error() != expected.Error() { + t.Fatalf("expected error on invalid mac - got %v", err) + } +} + +func TestServerSetAKEState_InvalidInput(t *testing.T) { + conf := opaque.DefaultConfiguration() + + /* + Test an invalid state + */ + + buf := internal.RandomBytes(conf.MAC.Size() + conf.KDF.Size() + 1) + + server, _ := conf.Server() + if err := server.SetAKEState(buf); err == nil || err.Error() != errInvalidStateLength.Error() { + t.Fatalf("Expected error for SetAKEState. want %q, got %q", errInvalidStateLength, err) + } + + /* + A state already exists. + */ + + credId := internal.RandomBytes(32) + seed := internal.RandomBytes(conf.Hash.Size()) + client, _ := conf.Client() + server, _ = conf.Server() + sk, pk := conf.KeyGen() + rec := buildRecord(credId, seed, []byte("yo"), pk, client, server) + ke1 := client.LoginInit([]byte("yo")) + _, _ = server.LoginInit(ke1, nil, sk, pk, seed, rec) + state := server.SerializeState() + if err := server.SetAKEState(state); err == nil || err.Error() != errStateExists.Error() { + t.Fatalf("Expected error for SetAKEState. want %q, got %q", errStateExists, err) + } +} From d922c6bdfa0219b24b47c893b88c9f978c1db568 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Sun, 3 Apr 2022 01:07:10 +0200 Subject: [PATCH 4/9] fix forgotten group identifier in messages for serialization and added runtime test to fail Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- client.go | 11 +++++++++-- deserializer.go | 5 +++++ internal/ake/client.go | 1 + internal/ake/server.go | 1 + internal/encoding/group.go | 12 ++++++------ server.go | 2 ++ 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/client.go b/client.go index 44210ee..1cb6843 100644 --- a/client.go +++ b/client.go @@ -74,7 +74,10 @@ func (c *Client) buildPRK(evaluation *group.Point) []byte { // RegistrationInit returns a RegistrationRequest message blinding the given password. func (c *Client) RegistrationInit(password []byte) *message.RegistrationRequest { m := c.OPRF.Blind(password) - return &message.RegistrationRequest{BlindedMessage: m} + return &message.RegistrationRequest{ + C: c.conf.OPRF, + BlindedMessage: m, + } } // RegistrationFinalizeWithNonce returns a RegistrationRecord message given the identities, server's @@ -120,6 +123,7 @@ func (c *Client) registrationFinalize( ) return &message.RegistrationRecord{ + G: c.conf.Group, PublicKey: clientPublicKey, MaskingKey: maskingKey, Envelope: envelope.Serialize(), @@ -130,7 +134,10 @@ func (c *Client) registrationFinalize( // clientInfo is optional client information sent in clear, and only authenticated in KE3. func (c *Client) LoginInit(password []byte) *message.KE1 { m := c.OPRF.Blind(password) - credReq := &message.CredentialRequest{BlindedMessage: m} + credReq := &message.CredentialRequest{ + C: c.conf.OPRF, + BlindedMessage: m, + } ke1 := c.Ake.Start(c.conf.Group) ke1.CredentialRequest = credReq c.Ake.Ke1 = ke1.Serialize() diff --git a/deserializer.go b/deserializer.go index a2eeafe..94e633c 100644 --- a/deserializer.go +++ b/deserializer.go @@ -10,6 +10,7 @@ package opaque import ( "errors" + "log" "github.com/bytemare/crypto/group" @@ -174,6 +175,7 @@ func (d *Deserializer) KE1(ke1 []byte) (*message.KE1, error) { } return &message.KE1{ + G: d.conf.Group, CredentialRequest: &message.CredentialRequest{ C: d.conf.OPRF, BlindedMessage: blindedMessage, @@ -221,7 +223,10 @@ func (d *Deserializer) KE2(ke2 []byte) (*message.KE2, error) { return nil, errInvalidServerEPK } + log.Printf("group %v", d.conf.Group) + return &message.KE2{ + G: d.conf.Group, CredentialResponse: cresp, NonceS: nonceS, EpkS: epks, diff --git a/internal/ake/client.go b/internal/ake/client.go index a25c012..a7bcc9a 100644 --- a/internal/ake/client.go +++ b/internal/ake/client.go @@ -53,6 +53,7 @@ func (c *Client) Start(cs group.Group) *message.KE1 { epk := c.SetValues(cs, nil, nil, 32) return &message.KE1{ + G: cs, NonceU: c.nonceU, EpkU: epk, } diff --git a/internal/ake/server.go b/internal/ake/server.go index 5af74ab..933a167 100644 --- a/internal/ake/server.go +++ b/internal/ake/server.go @@ -63,6 +63,7 @@ func (s *Server) Response( epk := s.SetValues(conf.Group, nil, nil, conf.NonceLen) ke2 := &message.KE2{ + G: conf.Group, CredentialResponse: response, NonceS: s.nonceS, EpkS: epk, diff --git a/internal/encoding/group.go b/internal/encoding/group.go index d65d358..39251b9 100644 --- a/internal/encoding/group.go +++ b/internal/encoding/group.go @@ -58,13 +58,13 @@ func SerializeScalar(s *group.Scalar, g group.Group) []byte { } // SerializePoint pads the given element if necessary. -func SerializePoint(e *group.Point, g group.Group) []byte { - return PadPoint(e.Bytes(), g) -} +func SerializePoint(p *group.Point, g group.Group) []byte { + length, ok := PointLength[g] + if !ok { + panic("invalid group identifier") + } -// PadPoint pads the encoded element if necessary. -func PadPoint(point []byte, g group.Group) []byte { - length := PointLength[g] + point := p.Bytes() for len(point) < length { point = append([]byte{0x00}, point...) diff --git a/server.go b/server.go index 9e43fa8..8010453 100644 --- a/server.go +++ b/server.go @@ -100,6 +100,7 @@ func (s *Server) RegistrationResponse( return &message.RegistrationResponse{ C: s.conf.OPRF, + G: s.conf.Group, EvaluatedMessage: z, Pks: serverPublicKey, } @@ -122,6 +123,7 @@ func (s *Server) credentialResponse( ) return &message.CredentialResponse{ + C: s.conf.OPRF, EvaluatedMessage: z, MaskingNonce: maskingNonce, MaskedResponse: maskedResponse, From 6f8b080474960b7a70f4dd45b7049f5fb1fed0c4 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Sun, 3 Apr 2022 01:09:07 +0200 Subject: [PATCH 5/9] move some things around Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- opaque.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/opaque.go b/opaque.go index 3c4e6f6..9dde12c 100644 --- a/opaque.go +++ b/opaque.go @@ -57,11 +57,11 @@ const ( ) var ( + errInvalidOPRFid = errors.New("invalid OPRF group id") errInvalidKDFid = errors.New("invalid KDF id") errInvalidMACid = errors.New("invalid MAC id") errInvalidHASHid = errors.New("invalid Hash id") errInvalidKSFid = errors.New("invalid KSF id") - errInvalidOPRFid = errors.New("invalid OPRF group id") errInvalidAKEid = errors.New("invalid AKE group id") ) @@ -124,8 +124,12 @@ func (c *Configuration) KeyGen() (secretKey, publicKey []byte) { return ake.KeyGen(group.Group(c.AKE)) } -// verify returns an error on the first non-compliant parameter, ni otherwise. +// verify returns an error on the first non-compliant parameter, nil otherwise. func (c *Configuration) verify() error { + if !oprf.Ciphersuite(c.OPRF).Available() { + return errInvalidOPRFid + } + if !hash.Hashing(c.KDF).Available() { return errInvalidKDFid } @@ -142,10 +146,6 @@ func (c *Configuration) verify() error { return errInvalidKSFid } - if !oprf.Ciphersuite(c.OPRF).Available() { - return errInvalidOPRFid - } - if !group.Group(c.AKE).Available() { return errInvalidAKEid } @@ -223,11 +223,11 @@ func DeserializeConfiguration(encoded []byte) (*Configuration, error) { Context: ctx, } - if _err := c.verify(); err != nil { - return nil, _err + if err = c.verify(); err != nil { + return nil, err } - return c, err + return c, nil } // ClientRecord is a server-side structure enabling the storage of user relevant information. From fadf48b5b2b111a46be8ef54a1a8d43e41bf6ba6 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 4 Apr 2022 22:26:10 +0200 Subject: [PATCH 6/9] add tests, rework fake credentials, refine examples, add licence headers Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- client.go | 1 + opaque.go | 42 ++++++++++++++++++-------- server.go | 9 +----- tests/client_test.go | 8 +++++ tests/deserializer_test.go | 32 ++++++++++++++++++++ tests/opaque_test.go | 61 ++++++++++++++++++++++++++++++++++++++ tests/server_test.go | 57 +++++++++++++++-------------------- tests/vectors_test.go | 18 +++++++---- 8 files changed, 169 insertions(+), 59 deletions(-) diff --git a/client.go b/client.go index 1cb6843..672e981 100644 --- a/client.go +++ b/client.go @@ -74,6 +74,7 @@ func (c *Client) buildPRK(evaluation *group.Point) []byte { // RegistrationInit returns a RegistrationRequest message blinding the given password. func (c *Client) RegistrationInit(password []byte) *message.RegistrationRequest { m := c.OPRF.Blind(password) + return &message.RegistrationRequest{ C: c.conf.OPRF, BlindedMessage: m, diff --git a/opaque.go b/opaque.go index 9dde12c..b8d2264 100644 --- a/opaque.go +++ b/opaque.go @@ -202,6 +202,34 @@ func (c *Configuration) Serialize() []byte { return encoding.Concat(b, encoding.EncodeVector(c.Context)) } +// GetFakeRecord creates a fake Client record to be used when no existing client record exists, +// to defend against client enumeration techniques. +func (c *Configuration) GetFakeRecord(credentialIdentifier []byte) (*ClientRecord, error) { + i, err := c.toInternal() + if err != nil { + return nil, err + } + + scalar := i.Group.NewScalar().Random() + publicKey := i.Group.Base().Mult(scalar) + maskingKey := RandomBytes(i.KDF.Size()) + envelope := make([]byte, internal.NonceLength+i.MAC.Size()) + + regRecord := &message.RegistrationRecord{ + G: i.Group, + PublicKey: publicKey, + MaskingKey: maskingKey, + Envelope: envelope, + } + + return &ClientRecord{ + CredentialIdentifier: credentialIdentifier, + ClientIdentity: nil, + RegistrationRecord: regRecord, + TestMaskNonce: nil, + }, nil +} + // DeserializeConfiguration decodes the input and returns a Parameter structure. func DeserializeConfiguration(encoded []byte) (*Configuration, error) { if len(encoded) < confLength+2 { // corresponds to the configuration length + 2-byte encoding of empty context @@ -223,7 +251,7 @@ func DeserializeConfiguration(encoded []byte) (*Configuration, error) { Context: ctx, } - if err = c.verify(); err != nil { + if err := c.verify(); err != nil { return nil, err } @@ -240,18 +268,6 @@ type ClientRecord struct { TestMaskNonce []byte } -// GetFakeEnvelope returns a byte array filled with 0s the length of a legitimate envelope size in the configuration's. -// This fake envelope byte array is used in the client enumeration mitigation scheme. -func GetFakeEnvelope(c *Configuration) []byte { - if !hash.Hashing(c.MAC).Available() { - panic(errInvalidMACid) - } - - envelopeSize := internal.NonceLength + internal.NewMac(c.MAC).Size() - - return make([]byte, envelopeSize) -} - // RandomBytes returns random bytes of length len (wrapper for crypto/rand). func RandomBytes(length int) []byte { return internal.RandomBytes(length) diff --git a/server.go b/server.go index 8010453..9f183ba 100644 --- a/server.go +++ b/server.go @@ -41,9 +41,6 @@ var ( // ErrInvalidOPRFSeedLength indicates that the OPRF seed is not of right length. ErrInvalidOPRFSeedLength = errors.New("input OPRF seed length is invalid (must be of hash output length)") - // ErrIdentityPKS indicates that the server public key is the group's identity point. - ErrIdentityPKS = errors.New("invalid server public key: pks is identity point") - // ErrZeroSKS indicates that the server's private key is a zero scalar. ErrZeroSKS = errors.New("server private key is zero") ) @@ -151,15 +148,11 @@ func (s *Server) verifyInitInput( return nil, ErrInvalidOPRFSeedLength } - pks, err := s.conf.Group.NewElement().Decode(serverPublicKey) + _, err = s.conf.Group.NewElement().Decode(serverPublicKey) if err != nil { return nil, fmt.Errorf("invalid server public key: %w", err) } - if pks.IsIdentity() { - return nil, ErrIdentityPKS - } - if len(record.Envelope) != s.conf.EnvelopeSize { return nil, ErrInvalidEnvelopeLength } diff --git a/tests/client_test.go b/tests/client_test.go index 90aefca..4c33a82 100644 --- a/tests/client_test.go +++ b/tests/client_test.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + package opaque_test import ( diff --git a/tests/deserializer_test.go b/tests/deserializer_test.go index 2aea804..2323aa6 100644 --- a/tests/deserializer_test.go +++ b/tests/deserializer_test.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + package opaque_test import ( @@ -17,6 +25,30 @@ var errInvalidMessageLength = errors.New("invalid message length for the configu Message Deserialization */ +func TestDeserializer(t *testing.T) { + // Test valid configurations + for _, conf := range confs { + if _, err := conf.Conf.Deserializer(); err != nil { + t.Fatalf("unexpected error on valid configuration: %v", err) + } + } + + // Test for an invalid configuration. + conf := &opaque.Configuration{ + OPRF: 0, + KDF: 0, + MAC: 0, + Hash: 0, + KSF: 0, + AKE: 0, + Context: nil, + } + + if _, err := conf.Deserializer(); err == nil { + t.Fatal("expected error on invalid configuration") + } +} + func TestDeserializeRegistrationRequest(t *testing.T) { c := opaque.DefaultConfiguration() diff --git a/tests/opaque_test.go b/tests/opaque_test.go index 0af66f5..36013a8 100644 --- a/tests/opaque_test.go +++ b/tests/opaque_test.go @@ -208,6 +208,43 @@ func testAuthentication(t *testing.T, p *testParams, record *opaque.ClientRecord return exportKeyLogin } +func isSameConf(a, b *opaque.Configuration) bool { + if a.OPRF != b.OPRF { + return false + } + if a.KDF != b.KDF { + return false + } + if a.MAC != b.MAC { + return false + } + if a.Hash != b.Hash { + return false + } + if a.KSF != b.KSF { + return false + } + if a.AKE != b.AKE { + return false + } + + return bytes.Equal(a.Context, b.Context) +} + +func TestConfiguration_Deserialization(t *testing.T) { + conf := opaque.DefaultConfiguration() + ser := conf.Serialize() + + conf2, err := opaque.DeserializeConfiguration(ser) + if err != nil { + t.Fatalf("unexpected error on valid configuration: %v", err) + } + + if !isSameConf(conf, conf2) { + t.Fatalf("Unexpected inequality:\n\t%v\n\t%v", conf, conf2) + } +} + /* The following tests look for failing conditions. */ @@ -365,3 +402,27 @@ func TestBadConfiguration(t *testing.T) { }) } } + +func TestFakeRecord(t *testing.T) { + // Test valid configurations + for _, conf := range confs { + if _, err := conf.Conf.GetFakeRecord(nil); err != nil { + t.Fatalf("unexpected error on valid configuration: %v", err) + } + } + + // Test for an invalid configuration. + conf := &opaque.Configuration{ + OPRF: 0, + KDF: 0, + MAC: 0, + Hash: 0, + KSF: 0, + AKE: 0, + Context: nil, + } + + if _, err := conf.GetFakeRecord(nil); err == nil { + t.Fatal("expected error on invalid configuration") + } +} diff --git a/tests/server_test.go b/tests/server_test.go index 545a84e..7b74541 100644 --- a/tests/server_test.go +++ b/tests/server_test.go @@ -1,18 +1,21 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2021 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + package opaque_test import ( "errors" - "log" "strings" "testing" - H2C "github.com/armfazh/h2c-go-ref" - "github.com/bytemare/crypto/group" - "github.com/bytemare/opaque" "github.com/bytemare/opaque/internal" "github.com/bytemare/opaque/internal/encoding" - "github.com/bytemare/opaque/message" ) var ( @@ -45,17 +48,6 @@ func TestServer_BadRegistrationRequest(t *testing.T) { } } -func TestIdentity(t *testing.T) { - g := group.P256Sha256 - log.Printf("Group: %v", g) - id := g.Identity() - log.Printf("Id: %v / %v / %v", id, id.Point.IsIdentity(), id.Point) - h, _ := H2C.P256_XMDSHA256_SSWU_RO_.Get(nil) - x := h.GetCurve().Identity().X() - y := h.GetCurve().Identity().Y() - log.Printf("\n%v\n%v", x, y) -} - func TestServerInit_InvalidPublicKey(t *testing.T) { /* Nil and invalid server public key @@ -76,14 +68,6 @@ func TestServerInit_InvalidPublicKey(t *testing.T) { !strings.HasPrefix(err.Error(), expected) { t.Fatalf("expected error on bad secret key - got %s", err) } - - expected = "invalid server public key: pks is identity point" - log.Printf("id : %v", server.GetConf().Group) - identity := server.GetConf().Group.Identity().Bytes() - if _, err := server.LoginInit(nil, nil, sk, identity, oprfSeed, nil); err == nil || - !strings.HasPrefix(err.Error(), expected) { - t.Fatalf("expected error on bad secret key - got %s", err) - } } } @@ -128,6 +112,22 @@ func TestServerInit_NilSecretKey(t *testing.T) { } } +func TestServerInit_ZeroSecretKey(t *testing.T) { + /* + Nil server secret key + */ + for _, conf := range confs { + server, _ := conf.Conf.Server() + sk := [32]byte{} + expected := "server private key is zero" + + if _, err := server.LoginInit(nil, nil, sk[:], nil, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expected) { + t.Fatalf("expected error on nil secret key - got %s", err) + } + } +} + func TestServerInit_InvalidEnvelope(t *testing.T) { /* Record envelope of invalid length @@ -170,17 +170,8 @@ func TestServerInit_InvalidEPKU(t *testing.T) { /* Invalid EPKU in KE1 */ - rec := &opaque.ClientRecord{ - CredentialIdentifier: internal.RandomBytes(32), - ClientIdentity: nil, - RegistrationRecord: &message.RegistrationRecord{ - MaskingKey: internal.RandomBytes(32), - }, - TestMaskNonce: nil, - } for _, conf := range confs { - rec.Envelope = opaque.GetFakeEnvelope(conf.Conf) server, _ := conf.Conf.Server() client, _ := conf.Conf.Client() ke1 := client.LoginInit([]byte("yo")).Serialize() diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 67371a8..879bba2 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -18,9 +18,11 @@ import ( "strings" "testing" + "github.com/bytemare/crypto/hash" "github.com/bytemare/crypto/ksf" "github.com/bytemare/opaque" + "github.com/bytemare/opaque/internal" "github.com/bytemare/opaque/internal/encoding" "github.com/bytemare/opaque/internal/oprf" "github.com/bytemare/opaque/message" @@ -135,10 +137,6 @@ func (v *vector) testRegistration(conf *opaque.Configuration, t *testing.T) { panic(err) } - if pks.IsIdentity() { - panic("identity") - } - regResp := server.RegistrationResponse(regReq, pks, v.Inputs.CredentialIdentifier, v.Inputs.OprfSeed) vRegResp, err := client.Deserialize.RegistrationResponse(v.Outputs.RegistrationResponse) @@ -180,6 +178,16 @@ func (v *vector) testRegistration(conf *opaque.Configuration, t *testing.T) { } } +func getFakeEnvelope(c *opaque.Configuration) []byte { + if !hash.Hashing(c.MAC).Available() { + panic(nil) + } + + envelopeSize := internal.NonceLength + internal.NewMac(c.MAC).Size() + + return make([]byte, envelopeSize) +} + func (v *vector) testLogin(conf *opaque.Configuration, t *testing.T) { // Client client, _ := conf.Client() @@ -211,7 +219,7 @@ func (v *vector) testLogin(conf *opaque.Configuration, t *testing.T) { record.RegistrationRecord = upload } else { - rec, err := server.Deserialize.RegistrationRecord(encoding.Concat3(v.Inputs.ClientPublicKey, v.Inputs.MaskingKey, opaque.GetFakeEnvelope(conf))) + rec, err := server.Deserialize.RegistrationRecord(encoding.Concat3(v.Inputs.ClientPublicKey, v.Inputs.MaskingKey, getFakeEnvelope(conf))) if err != nil { t.Fatal(err) } From 509b96f0fed3c9a65484741bd26bab4e79cfa0d5 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 4 Apr 2022 22:54:22 +0200 Subject: [PATCH 7/9] don't check for identity point on deserialization as it's now integrated in Decode() Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- deserializer.go | 32 -------------------------------- go.mod | 2 +- go.sum | 4 ++-- internal/masking/masking.go | 4 ---- 4 files changed, 3 insertions(+), 39 deletions(-) diff --git a/deserializer.go b/deserializer.go index 94e633c..6e3a1a0 100644 --- a/deserializer.go +++ b/deserializer.go @@ -45,10 +45,6 @@ func (d *Deserializer) RegistrationRequest(registrationRequest []byte) (*message return nil, errInvalidBlindedData } - if blindedMessage.IsIdentity() { - return nil, errInvalidBlindedData - } - return &message.RegistrationRequest{C: d.conf.OPRF, BlindedMessage: blindedMessage}, nil } @@ -70,19 +66,11 @@ func (d *Deserializer) RegistrationResponse(registrationResponse []byte) (*messa return nil, errInvalidEvaluatedData } - if evaluatedMessage.IsIdentity() { - return nil, errInvalidEvaluatedData - } - pks, err := d.conf.Group.NewElement().Decode(registrationResponse[d.conf.OPRFPointLength:]) if err != nil { return nil, errInvalidServerPK } - if pks.IsIdentity() { - return nil, errInvalidServerPK - } - return &message.RegistrationResponse{ C: d.conf.OPRF, G: d.conf.Group, @@ -111,10 +99,6 @@ func (d *Deserializer) RegistrationRecord(record []byte) (*message.RegistrationR return nil, errInvalidClientPK } - if pku.IsIdentity() { - return nil, errInvalidClientPK - } - return &message.RegistrationRecord{ G: d.conf.Group, PublicKey: pku, @@ -132,10 +116,6 @@ func (d *Deserializer) deserializeCredentialResponse( return nil, errInvalidEvaluatedData } - if data.IsIdentity() { - return nil, errInvalidEvaluatedData - } - return &message.CredentialResponse{ C: d.conf.OPRF, EvaluatedMessage: data, @@ -159,10 +139,6 @@ func (d *Deserializer) KE1(ke1 []byte) (*message.KE1, error) { return nil, errInvalidBlindedData } - if blindedMessage.IsIdentity() { - return nil, errInvalidBlindedData - } - nonceU := ke1[d.conf.OPRFPointLength : d.conf.OPRFPointLength+d.conf.NonceLen] epku, err := d.conf.Group.NewElement().Decode(ke1[d.conf.OPRFPointLength+d.conf.NonceLen:]) @@ -170,10 +146,6 @@ func (d *Deserializer) KE1(ke1 []byte) (*message.KE1, error) { return nil, errInvalidClientEPK } - if epku.IsIdentity() { - return nil, errInvalidClientEPK - } - return &message.KE1{ G: d.conf.Group, CredentialRequest: &message.CredentialRequest{ @@ -219,10 +191,6 @@ func (d *Deserializer) KE2(ke2 []byte) (*message.KE2, error) { return nil, errInvalidServerEPK } - if epks.IsIdentity() { - return nil, errInvalidServerEPK - } - log.Printf("group %v", d.conf.Group) return &message.KE2{ diff --git a/go.mod b/go.mod index 3906b3e..46e07b2 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/bytemare/opaque go 1.18 -require github.com/bytemare/crypto v0.2.6 +require github.com/bytemare/crypto v0.2.7 require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect diff --git a/go.sum b/go.sum index 620fbf0..c5497cf 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/armfazh/h2c-go-ref v0.0.0-20220222212046-ff45165972af h1:3bAG1kgYCxLL github.com/armfazh/h2c-go-ref v0.0.0-20220222212046-ff45165972af/go.mod h1:mtUQsERQBqNOHy8yMHF+K6tvXNgjPpTk8k7VYxKK6pU= github.com/armfazh/tozan-ecc v0.1.4 h1:PnCI4iLifKiXcDBVX6B5LqCWreN56lxlspgZdVdOhvA= github.com/armfazh/tozan-ecc v0.1.4/go.mod h1:u25eZC5Z8uJFQxJxGBz1Blfii/7m3DfmwX0vFnwtG9I= -github.com/bytemare/crypto v0.2.6 h1:cRU9uIvW46LoNdDVS7IbPSPLr8RkTWNfeGANGDFGxzw= -github.com/bytemare/crypto v0.2.6/go.mod h1:GRN/NPLEuubCbo8Ub8z2RdLJO9HvQDEaSZWbeSDVyJ4= +github.com/bytemare/crypto v0.2.7 h1:bh8gF/FthYyLzsRNDM/lthENPW6MvacQZ90eS8zUTrE= +github.com/bytemare/crypto v0.2.7/go.mod h1:GRN/NPLEuubCbo8Ub8z2RdLJO9HvQDEaSZWbeSDVyJ4= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/internal/masking/masking.go b/internal/masking/masking.go index d172ebb..7f21d72 100644 --- a/internal/masking/masking.go +++ b/internal/masking/masking.go @@ -67,9 +67,5 @@ func Unmask( return nil, nil, nil, errInvalidPKS } - if serverPublicKey.IsIdentity() { - return nil, nil, nil, errInvalidPKS - } - return serverPublicKey, serverPublicKeyBytes, envelope, nil } From 1bf5bba44212a4ca7f7b79f653b3dc17f902300b Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 4 Apr 2022 22:54:38 +0200 Subject: [PATCH 8/9] add more examples Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- examples_test.go | 257 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) diff --git a/examples_test.go b/examples_test.go index 19ee591..0d7df34 100644 --- a/examples_test.go +++ b/examples_test.go @@ -11,8 +11,10 @@ package opaque_test import ( "bytes" "crypto" + "encoding/hex" "fmt" "log" + "reflect" "github.com/bytemare/crypto/ksf" @@ -109,6 +111,105 @@ func Example_serverSetup() { // Output: OPAQUE server values initialized. } +// Example_Deserialization demonstrates a couple of ways to deserialize OPAQUE protocol messages. +// Messages are formatted in function of the configuration context they are exchanged in. Hence, +// we need the corresponding configuration. We can then directly deserialize messages from a +// Configuration or pass them to Client or Server instances which can do it as well. +// You must know in advance what message you are expecting, and call the appropriate +// deserialization function. +func Example_deserialization() { + // Let's work with this RegistrationRequest message we received on the wire. + registrationMessage := []byte{ + 152, + 87, + 225, + 105, + 74, + 245, + 80, + 197, + 21, + 229, + 106, + 145, + 3, + 41, + 42, + 208, + 122, + 1, + 75, + 2, + 7, + 8, + 211, + 223, + 87, + 172, + 75, + 21, + 31, + 88, + 211, + 35, + } + + // Pick your configuration. + conf := opaque.DefaultConfiguration() + + // You can directly deserialize and test the message's validity in that configuration by getting a deserializer. + deserializer, err := conf.Deserializer() + if err != nil { + log.Fatalln(err) + } + + requestD, err := deserializer.RegistrationRequest(registrationMessage) + if err != nil { + log.Fatalln(err) + } + + // Or if you already have a Server instance, you can use that also. + server, err := conf.Server() + if err != nil { + log.Fatalln(err) + } + + requestS, err := server.Deserialize.RegistrationRequest(registrationMessage) + if err != nil { + // The error message will tell us what's wrong. + log.Fatalln(err) + } + + // Alternatively, a Client instance can do that as well. + client, err := conf.Client() + if err != nil { + // The error message will tell us what's wrong. + log.Fatalln(err) + } + + requestC, err := client.Deserialize.RegistrationRequest(registrationMessage) + if err != nil { + // The error message will tell us what's wrong. + log.Fatalln(err) + } + + // All these yield the same message. The following is just a test to proof that point. + { + if !reflect.DeepEqual(requestD, requestS) || + !reflect.DeepEqual(requestD, requestC) || + !reflect.DeepEqual(requestS, requestC) { + log.Fatalf("Unexpected divergent RegistrationMessages:\n\t- %v\n\t- %v\n\t- %v", + hex.EncodeToString(requestD.Serialize()), + hex.EncodeToString(requestS.Serialize()), + hex.EncodeToString(requestC.Serialize())) + } + + fmt.Println("OPAQUE messages deserialization is easy!") + } + + // Output: OPAQUE messages deserialization is easy! +} + // Example_Registration demonstrates in a single function the interactions between a client and a server for the // registration phase. This is of course a proof-of-concept demonstration, as client and server execute separately. // The server outputs a ClientRecord and the credential identifier. The latter is a unique identifier for a given @@ -307,3 +408,159 @@ func Example_loginKeyExchange() { // OPAQUE registration is easy! // OPAQUE is much awesome! } + +// Example_FakeResponse shows how to counter some client enumeration attacks by faking an existing client entry. +// Precompute the fake client record, and return it when no valid record was found. +// Use this with the server's LoginInit function whenever a client wants to retrieve an envelope but a client +// entry does not exist. Failing to do so results in an attacker being able to enumerate users. +func Example_fakeResponse() { + // The server must have been set up with its long term values once. So we're calling this, here, for the demo. + { + Example_serverSetup() + } + + // Precompute the fake client record, and return it when no valid record was found. The malicious client will + // purposefully fail, but can't determine the difference with an existing client record. Choose the same + // configuration as in your app. + conf := opaque.DefaultConfiguration() + fakeRecord, err := conf.GetFakeRecord([]byte("fake_client")) + if err != nil { + log.Fatalln(err) + } + + // Later, during protocol execution, l et's say this is the fraudulent login message we received, + // for which no client entry exists. + message1 := []byte{ + 180, + 211, + 102, + 100, + 94, + 122, + 227, + 128, + 249, + 212, + 118, + 225, + 49, + 158, + 103, + 193, + 130, + 31, + 122, + 93, + 61, + 251, + 252, + 78, + 38, + 199, + 137, + 131, + 81, + 151, + 145, + 57, + 14, + 165, + 40, + 252, + 96, + 155, + 67, + 147, + 176, + 53, + 62, + 133, + 253, + 187, + 32, + 198, + 6, + 124, + 17, + 145, + 159, + 64, + 217, + 61, + 139, + 178, + 41, + 150, + 127, + 194, + 135, + 140, + 32, + 151, + 134, + 239, + 75, + 150, + 11, + 251, + 254, + 16, + 72, + 28, + 31, + 211, + 1, + 48, + 15, + 199, + 45, + 196, + 35, + 74, + 30, + 130, + 155, + 85, + 108, + 114, + 15, + 144, + 77, + 48, + } + + // Continue as usual, using the fake record in lieu of the (non-)existing one. The server the sends + // back the serialized ke2 message message2. + var message2 []byte + { + serverID := []byte("server") + server, err := conf.Server() + if err != nil { + log.Fatalln(err) + } + + ke1, err := server.Deserialize.KE1(message1) + if err != nil { + log.Fatalln(err) + } + + ke2, err := server.LoginInit(ke1, serverID, serverPrivateKey, serverPublicKey, secretOprfSeed, fakeRecord) + if err != nil { + log.Fatalln(err) + } + + message2 = ke2.Serialize() + } + + // The following is just a test to check everything went fine. + { + if len(message2) == 0 { + log.Fatalln("Fake KE2 is unexpectedly empty.") + } + + fmt.Println("Thwarting OPAQUE client enumeration is easy!") + } + + // Output: OPAQUE server values initialized. + // Thwarting OPAQUE client enumeration is easy! +} From d57658ee3a7842db0dadbf0fbed1d98487b79746 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 4 Apr 2022 22:57:07 +0200 Subject: [PATCH 9/9] factorize some lines Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- opaque.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/opaque.go b/opaque.go index b8d2264..0ae6ec2 100644 --- a/opaque.go +++ b/opaque.go @@ -212,14 +212,12 @@ func (c *Configuration) GetFakeRecord(credentialIdentifier []byte) (*ClientRecor scalar := i.Group.NewScalar().Random() publicKey := i.Group.Base().Mult(scalar) - maskingKey := RandomBytes(i.KDF.Size()) - envelope := make([]byte, internal.NonceLength+i.MAC.Size()) regRecord := &message.RegistrationRecord{ G: i.Group, PublicKey: publicKey, - MaskingKey: maskingKey, - Envelope: envelope, + MaskingKey: RandomBytes(i.KDF.Size()), + Envelope: make([]byte, internal.NonceLength+i.MAC.Size()), } return &ClientRecord{