From 390cb9dd8bc56fe3f70612e9b041d43e7cba99b7 Mon Sep 17 00:00:00 2001 From: Daniel Bourdrez <3641580+bytemare@users.noreply.github.com> Date: Sat, 5 Oct 2024 00:30:32 +0200 Subject: [PATCH] Added JSONReGetGroup for JSON unmarshalling of group scalars and elements (#3) Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- encoding/json.go | 61 ++++++++++++++++ internal/misc.go | 9 +++ tests/bench_test.go | 2 +- tests/element_test.go | 2 +- tests/encoding_test.go | 52 +++++++++++++- tests/groups_test.go | 2 +- tests/h2c_test.go | 2 +- tests/json_test.go | 133 +++++++++++++++++++++++++++++++++++ tests/nist_test.go | 2 +- tests/ristretto_hash_test.go | 2 +- tests/ristretto_test.go | 2 +- tests/scalar_test.go | 2 +- tests/table_test.go | 2 +- tests/utils_test.go | 2 +- 14 files changed, 264 insertions(+), 11 deletions(-) create mode 100644 encoding/json.go create mode 100644 tests/json_test.go diff --git a/encoding/json.go b/encoding/json.go new file mode 100644 index 0000000..e35c507 --- /dev/null +++ b/encoding/json.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2020-2024 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 encoding provides serde utilities. +package encoding + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/bytemare/ecc" + "github.com/bytemare/ecc/internal" +) + +func jsonReGetField(key, s, catch string) (string, error) { + r := fmt.Sprintf(`%q:%s`, key, catch) + re := regexp.MustCompile(r) + matches := re.FindStringSubmatch(s) + + if len(matches) != 2 { + return "", internal.ErrDecodingInvalidJSONEncoding + } + + return matches[1], nil +} + +// JSONReGetGroup attempts to find the group JSON encoding in s. The optional key argument overrides the default key the +// regex will use to look for the group. +func JSONReGetGroup(s string, key ...string) (ecc.Group, error) { + reKey := "group" + if len(key) != 0 && key[0] != "" { + reKey = key[0] + } + + f, err := jsonReGetField(reKey, s, `(\w+)`) + if err != nil { + return 0, err + } + + i, err := strconv.Atoi(f) + if err != nil { + return 0, fmt.Errorf("failed to read Group: %w", err) + } + + if i < 0 || i > 63 { + return 0, internal.ErrInvalidGroup + } + + c := ecc.Group(i) + if !c.Available() { + return 0, internal.ErrInvalidGroup + } + + return c, nil +} diff --git a/internal/misc.go b/internal/misc.go index 38d4f5d..84f6ce7 100644 --- a/internal/misc.go +++ b/internal/misc.go @@ -16,6 +16,9 @@ import ( ) var ( + // ErrInvalidGroup indicates usage of an unavailable or invalid group. + ErrInvalidGroup = errors.New("invalid group") + // ErrParamNilScalar indicates a forbidden nil or empty scalar. ErrParamNilScalar = errors.New("nil or empty scalar") @@ -54,6 +57,12 @@ var ( // ErrUInt64TooBig indicates that the scalar is higher than the allowed values for uint64. ErrUInt64TooBig = errors.New("scalar is too big to be uint64") + + // ErrDecodingInvalidLength indicates an invalid encoding length. + ErrDecodingInvalidLength = errors.New("invalid encoding length") + + // ErrDecodingInvalidJSONEncoding indicates an invalid JSON encoding. + ErrDecodingInvalidJSONEncoding = errors.New("invalid JSON encoding") ) // An Encoder can encode itself to machine or human-readable forms. diff --git a/tests/bench_test.go b/tests/bench_test.go index 267113f..e2b0818 100644 --- a/tests/bench_test.go +++ b/tests/bench_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "bytes" diff --git a/tests/element_test.go b/tests/element_test.go index 6a7b5bf..57c466b 100644 --- a/tests/element_test.go +++ b/tests/element_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "encoding/hex" diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 9196b14..0204636 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "bytes" @@ -19,6 +19,7 @@ import ( "testing" "github.com/bytemare/ecc" + eccEncoding "github.com/bytemare/ecc/encoding" ) type serde interface { @@ -263,3 +264,52 @@ func TestEncoding_Hex_Fails(t *testing.T) { } }) } + +func TestJSONReGetGroup(t *testing.T) { + testAllGroups(t, func(group *testGroup) { + test := struct { + Group ecc.Group `json:"group"` + Int int `json:"int"` + }{ + Group: group.group, + Int: 1, + } + + enc, err := json.Marshal(test) + if err != nil { + t.Fatal(err) + } + + g, err := eccEncoding.JSONReGetGroup(string(enc)) + if err != nil { + t.Fatal(err) + } + + if g != group.group { + t.Fatal(errExpectedEquality) + } + + // with another key + test2 := struct { + Group ecc.Group `json:"ciphersuite"` + Int int `json:"int"` + }{ + Group: group.group, + Int: 1, + } + + enc, err = json.Marshal(test2) + if err != nil { + t.Fatal(err) + } + + g, err = eccEncoding.JSONReGetGroup(string(enc), "ciphersuite") + if err != nil { + t.Fatal(err) + } + + if g != group.group { + t.Fatal(errExpectedEquality) + } + }) +} diff --git a/tests/groups_test.go b/tests/groups_test.go index f366d00..9e2b95d 100644 --- a/tests/groups_test.go +++ b/tests/groups_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "encoding/hex" diff --git a/tests/h2c_test.go b/tests/h2c_test.go index 042ab57..aeac4b8 100644 --- a/tests/h2c_test.go +++ b/tests/h2c_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "crypto/elliptic" diff --git a/tests/json_test.go b/tests/json_test.go new file mode 100644 index 0000000..2dc5db3 --- /dev/null +++ b/tests/json_test.go @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2020-2024 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 ecc_test + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/bytemare/ecc" + eccEncoding "github.com/bytemare/ecc/encoding" + "github.com/bytemare/ecc/internal" +) + +func replaceStringInBytes(data []byte, old, new string) []byte { + s := string(data) + s = strings.Replace(s, old, new, 1) + + return []byte(s) +} + +type jsonTesterBaddie struct { + key, value, expectedError string +} + +func testJSONBaddie(in any, baddie jsonTesterBaddie) error { + data, err := json.Marshal(in) + if err != nil { + return err + } + + data = replaceStringInBytes(data, baddie.key, baddie.value) + + _, err = eccEncoding.JSONReGetGroup(string(data)) + + if len(baddie.expectedError) != 0 { // we're expecting an error + if err == nil || + !strings.HasPrefix(err.Error(), baddie.expectedError) { + return fmt.Errorf("expected error %q, got %q", baddie.expectedError, err) + } + } else { + if err != nil { + return fmt.Errorf("unexpected error %q", err) + } + } + + return nil +} + +func jsonTester(badJSONErr string, in any) error { + // JSON: bad json + baddie := jsonTesterBaddie{ + key: "\"group\"", + value: "bad", + expectedError: "invalid character 'b' looking for beginning of object key string", + } + + if err := testJSONBaddie(in, baddie); err != nil { + // return err + } + + // UnmarshallJSON: bad group + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":2, \"oldGroup\"", + expectedError: internal.ErrInvalidGroup.Error(), + } + + if err := testJSONBaddie(in, baddie); err != nil { + return err + } + + // UnmarshallJSON: bad ciphersuite + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":70, \"oldGroup\"", + expectedError: internal.ErrInvalidGroup.Error(), + } + + if err := testJSONBaddie(in, baddie); err != nil { + return err + } + + // UnmarshallJSON: bad ciphersuite + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":-1, \"oldGroup\"", + expectedError: badJSONErr, + } + + if err := testJSONBaddie(in, baddie); err != nil { + return err + } + + // UnmarshallJSON: bad ciphersuite + overflow := "9223372036854775808" // MaxInt64 + 1 + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":" + overflow + ", \"oldGroup\"", + expectedError: "failed to read Group: strconv.Atoi: parsing \"9223372036854775808\": value out of range", + } + + if err := testJSONBaddie(in, baddie); err != nil { + return err + } + + return nil +} + +func TestJSONReGetGroup_BadString(t *testing.T) { + testAllGroups(t, func(group *testGroup) { + test := struct { + Group ecc.Group `json:"group"` + Int int `json:"int"` + }{ + Group: group.group, + Int: 1, + } + + // JSON: bad json + errInvalidJSON := "invalid JSON encoding" + if err := jsonTester(errInvalidJSON, test); err != nil { + t.Fatal(err) + } + }) +} diff --git a/tests/nist_test.go b/tests/nist_test.go index 3f93482..b75d4d7 100644 --- a/tests/nist_test.go +++ b/tests/nist_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "crypto/elliptic" diff --git a/tests/ristretto_hash_test.go b/tests/ristretto_hash_test.go index 21cfb10..086e9e5 100644 --- a/tests/ristretto_hash_test.go +++ b/tests/ristretto_hash_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "bytes" diff --git a/tests/ristretto_test.go b/tests/ristretto_test.go index dc3816a..1acca0b 100644 --- a/tests/ristretto_test.go +++ b/tests/ristretto_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "bytes" diff --git a/tests/scalar_test.go b/tests/scalar_test.go index 496b44a..1a0ffe1 100644 --- a/tests/scalar_test.go +++ b/tests/scalar_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "bytes" diff --git a/tests/table_test.go b/tests/table_test.go index 81ea2ab..a4f56d0 100644 --- a/tests/table_test.go +++ b/tests/table_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "crypto" diff --git a/tests/utils_test.go b/tests/utils_test.go index 29fb549..f6b626d 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -6,7 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package group_test +package ecc_test import ( "encoding/hex"