Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ED25519 ACVP Testing #1818

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crypto/fipsmodule/curve25519/curve25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,13 @@ int ED25519_verify(const uint8_t *message, size_t message_len,
return res;
}

int ED25519_check_public_key(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]) {
#if defined(CURVE25519_S2N_BIGNUM_CAPABLE)
return ed25519_check_public_key_s2n_bignum(public_key);
#else
return ed25519_check_public_key_nohw(public_key);
#endif
}

void X25519_public_from_private(
uint8_t out_public_value[X25519_PUBLIC_VALUE_LEN],
Expand Down
8 changes: 8 additions & 0 deletions crypto/fipsmodule/curve25519/curve25519_nohw.c
Original file line number Diff line number Diff line change
Expand Up @@ -2043,3 +2043,11 @@ int ed25519_verify_nohw(uint8_t R_computed_encoded[32],

return 1;
}

int ed25519_check_public_key_nohw(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]) {
ge_p3 A;
if (!x25519_ge_frombytes_vartime(&A, public_key)) {
return 0;
}
return 1;
}
8 changes: 8 additions & 0 deletions crypto/fipsmodule/curve25519/curve25519_s2n_bignum_asm.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,12 @@ int ed25519_verify_s2n_bignum(uint8_t R_computed_encoded[32],
return 1;
}

int ed25519_check_public_key_s2n_bignum(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]) {
uint64_t A[8] = {0};
if (edwards25519_decode_selector(A, public_key) != 0) {
return 0;
}
return 1;
}

#endif
5 changes: 5 additions & 0 deletions crypto/fipsmodule/curve25519/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ void ed25519_sha512(uint8_t out[SHA512_DIGEST_LENGTH],
const void *input1, size_t len1, const void *input2, size_t len2,
const void *input3, size_t len3);


int ed25519_check_public_key_s2n_bignum(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]);
int ed25519_check_public_key_nohw(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]);
OPENSSL_EXPORT int ED25519_check_public_key(const uint8_t public_key[ED25519_PUBLIC_KEY_LEN]);

#if defined(__cplusplus)
} // extern C
#endif
Expand Down
308 changes: 308 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/eddsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

package subprocess

import (
"encoding/json"
"fmt"
"strings"
)

// NIST ACVP EDDSA Schema: https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html
type eddsa struct{}

func (e *eddsa) Process(vectorSet []byte, m Transactable) (interface{}, error) {
var vs struct {
Mode string `json:"mode"`
TestGroups json.RawMessage `json:"testGroups"`
}

if err := json.Unmarshal(vectorSet, &vs); err != nil {
return nil, err
}

var processTestGroups func(json.RawMessage, Transactable) (interface{}, error)

switch {
case strings.EqualFold(vs.Mode, "keyGen"):
processTestGroups = processEddsaKeyGenTestGroup
case strings.EqualFold(vs.Mode, "keyVer"):
processTestGroups = processEddsaKeyVerTestGroup
case strings.EqualFold(vs.Mode, "sigGen"):
processTestGroups = processEddsaSigGenTestGroup
case strings.EqualFold(vs.Mode, "sigVer"):
processTestGroups = processEddsaSigVerTestGroup
default:
return nil, fmt.Errorf("unsupported EDDSA mode %q", vs.Mode)
}

return processTestGroups(vs.TestGroups, m)
}

func processEddsaKeyGenTestGroup(testGroups json.RawMessage, m Transactable) (interface{}, error) {
var groups []eddsaKeyGenTestGroup
if err := json.Unmarshal(testGroups, &groups); err != nil {
return nil, err
}

var ret []eddsaKeyGenTestGroupResponse

for _, group := range groups {
response := eddsaKeyGenTestGroupResponse{
ID: group.ID,
}

if group.Type != "AFT" {
return nil, fmt.Errorf("unsupported test type %q", group.Type)
}

for _, test := range group.Tests {
result, err := m.Transact("EDDSA/"+string(group.Curve)+"/keyGen", 2)
if err != nil {
return nil, err
}

response.Tests = append(response.Tests, eddsaKeyGenTestCaseResponse{
ID: test.ID,
D: result[0],
Q: result[1],
})
}

ret = append(ret, response)
}

return ret, nil
}

func processEddsaKeyVerTestGroup(testGroups json.RawMessage, m Transactable) (interface{}, error) {
var groups []eddsaKeyVerTestGroup
if err := json.Unmarshal(testGroups, &groups); err != nil {
return nil, err
}

var ret []eddsaKeyVerTestGroupResponse

for _, group := range groups {
if group.Type != "AFT" {
return nil, fmt.Errorf("unsupported test type %q", group.Type)
}

response := eddsaKeyVerTestGroupResponse{
ID: group.ID,
}

for _, test := range group.Tests {
results, err := m.Transact("EDDSA/"+string(group.Curve)+"/keyVer", 1, test.Q)
if err != nil {
return nil, err
}

var passed *bool
if len(results[0]) == 1 {
val := results[0][0] == 1
passed = &val
}

response.Tests = append(response.Tests, eddsaKeyVerTestCaseResponse{
ID: test.ID,
Passed: passed,
})
}

ret = append(ret, response)
}

return ret, nil
}

func processEddsaSigGenTestGroup(testGroups json.RawMessage, m Transactable) (interface{}, error) {
var groups []eddsaSigGenTestGroup
if err := json.Unmarshal(testGroups, &groups); err != nil {
return nil, err
}

var ret []eddsaSigGenTestGroupResponse

for _, group := range groups {
if group.Type != "AFT" && group.Type != "BFT" {
return nil, fmt.Errorf("unsupported test type %q", group.Type)
}

results, err := m.Transact("EDDSA/"+string(group.Curve)+"/keyGen", 2)
if err != nil {
return nil, err
}

seed := results[0]

response := eddsaSigGenTestGroupResponse{
ID: group.ID,
Q: results[1],
}

for _, test := range group.Tests {
results, err := m.Transact("EDDSA/"+string(group.Curve)+"/sigGen", 1, seed, test.Message)
if err != nil {
return nil, err
}

response.Tests = append(response.Tests, eddsaSigGenTestCaseResponse{
ID: test.ID,
Signature: results[0],
})
}

ret = append(ret, response)
}

return ret, nil
}

func processEddsaSigVerTestGroup(testGroups json.RawMessage, m Transactable) (interface{}, error) {
var groups []eddsaSigVerTestGroup
if err := json.Unmarshal(testGroups, &groups); err != nil {
return nil, err
}

var ret []eddsaSigVerTestGroupResponse

for _, group := range groups {
if group.Type != "AFT" {
return nil, fmt.Errorf("unsupported test type %q", group.Type)
}

response := eddsaSigVerTestGroupResponse{
ID: group.ID,
}

for _, test := range group.Tests {
results, err := m.Transact("EDDSA/"+string(group.Curve)+"/sigVer", 1, test.Message, test.Q, test.Signature)
if err != nil {
return nil, err
}

var passed *bool
if len(results[0]) == 1 {
val := results[0][0] == 1
passed = &val
}

response.Tests = append(response.Tests, eddsaSigVerTestCaseResponse{
ID: test.ID,
Passed: passed,
})
}

ret = append(ret, response)
}

return ret, nil
}

const Ed25519 EDDSACurve = "ED-25519"

type EDDSACurve string

func (e *EDDSACurve) UnmarshalJSON(v []byte) error {
var str string

if err := json.Unmarshal(v, &str); err != nil {
return err
}

switch {
case strings.EqualFold(str, "ED-25519"):
*e = Ed25519
default:
return fmt.Errorf("unsupported EDDSA curve: %q", str)
}

return nil
}

type eddsaKeyGenTestGroup struct {
ID uint64 `json:"tgId"`
Curve EDDSACurve `json:"curve"`
Type string `json:"testType"`
Tests []struct {
ID uint64 `json:"tcId"`
}
}

type eddsaKeyVerTestGroup struct {
ID uint64 `json:"tgId"`
Curve EDDSACurve `json:"curve"`
Type string `json:"testType"`
Tests []struct {
ID uint64 `json:"tcId"`
Q hexEncodedByteString `json:"q"`
}
}

type eddsaSigGenTestGroup struct {
ID uint64 `json:"tgId"`
Curve EDDSACurve `json:"curve"`
Prehash bool `json:"prehash"`
Type string `json:"testType"`
Tests []struct {
ID uint64 `json:"tcId"`
Message hexEncodedByteString `json:"message"`
}
}

type eddsaSigVerTestGroup struct {
ID uint64 `json:"tgId"`
Curve EDDSACurve `json:"curve"`
Prehash bool `json:"prehash"`
Type string `json:"testType"`
Tests []struct {
ID uint64 `json:"tcId"`
Message hexEncodedByteString `json:"message"`
Q hexEncodedByteString `json:"q"`
Signature hexEncodedByteString `json:"signature"`
}
}

type eddsaKeyGenTestGroupResponse struct {
ID uint64 `json:"tgId"`
Tests []eddsaKeyGenTestCaseResponse `json:"tests"`
}

type eddsaKeyGenTestCaseResponse struct {
ID uint64 `json:"tcId"`
D hexEncodedByteString `json:"d"`
Q hexEncodedByteString `json:"q"`
}

type eddsaKeyVerTestGroupResponse struct {
ID uint64 `json:"tgId"`
Tests []eddsaKeyVerTestCaseResponse `json:"tests"`
}

type eddsaKeyVerTestCaseResponse struct {
ID uint64 `json:"tcId"`
Passed *bool `json:"testPassed"`
}

type eddsaSigGenTestGroupResponse struct {
ID uint64 `json:"tgId"`
Q hexEncodedByteString `json:"q"`
Tests []eddsaSigGenTestCaseResponse `json:"tests"`
}

type eddsaSigGenTestCaseResponse struct {
ID uint64 `json:"tcId"`
Signature hexEncodedByteString `json:"signature"`
}

type eddsaSigVerTestGroupResponse struct {
ID uint64 `json:"tgId"`
Tests []eddsaSigVerTestCaseResponse `json:"tests"`
}

type eddsaSigVerTestCaseResponse struct {
ID uint64 `json:"tcId"`
Passed *bool `json:"testPassed"`
}
1 change: 1 addition & 0 deletions util/fipstools/acvp/acvptool/subprocess/subprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess
"KAS-FFC-SSC": &kasDH{},
"PBKDF": &pbkdf{},
"ML-KEM": &mlKem{},
"EDDSA": &eddsa{},
}
m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}

Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both expected responses for SigVer and KeyVer are false in this file. Should we include 2 Passed:true tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me pull the demo vectors again, I'm pretty sure there were truthy test cases but the test trim tool might have pruned them out since it's agnostic to the tests themselves.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline, but the trim vectors tool intent is to only leave one ACVP test per ACVP test group. So this is currently an expected outcome behavior that this trimming is exhaustive regardless of the perceived of the test. It would appear this level of ACVP testing in the repository is more to confirm the functionality of the tool, but may not be an exhaustive representation of the full suite of tests you would get from NIST in this format.

Long-term it would be nice to have something similar to our crypfuzz framework where we stash/keep a much larger set of ACVP vectors and use that it do additional exhaustive testing of our algorithms. Maybe a future intern project?

For now I confirmed that the vectors I validated with the NIST demo server had the variants in question that you referenced and successfully passed.

Binary file not shown.
5 changes: 4 additions & 1 deletion util/fipstools/acvp/acvptool/test/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@
{"Wrapper": "modulewrapper", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KDA-HKDF.bz2", "Out": "expected/KDA-HKDF.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/KDA-OneStep.bz2", "Out": "expected/KDA-OneStep.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"}
{"Wrapper": "modulewrapper", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/EDDSA-KeyGen.bz2"},
{"Wrapper": "modulewrapper", "In": "vectors/EDDSA-SigGen.bz2"}
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading