diff --git a/crypto/fipsmodule/curve25519/curve25519.c b/crypto/fipsmodule/curve25519/curve25519.c index 5717984a61..50aa6cdeb3 100644 --- a/crypto/fipsmodule/curve25519/curve25519.c +++ b/crypto/fipsmodule/curve25519/curve25519.c @@ -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], diff --git a/crypto/fipsmodule/curve25519/curve25519_nohw.c b/crypto/fipsmodule/curve25519/curve25519_nohw.c index 32d946f5c4..d51c94c261 100644 --- a/crypto/fipsmodule/curve25519/curve25519_nohw.c +++ b/crypto/fipsmodule/curve25519/curve25519_nohw.c @@ -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; +} diff --git a/crypto/fipsmodule/curve25519/curve25519_s2n_bignum_asm.c b/crypto/fipsmodule/curve25519/curve25519_s2n_bignum_asm.c index 59be5df878..50e52f02c1 100644 --- a/crypto/fipsmodule/curve25519/curve25519_s2n_bignum_asm.c +++ b/crypto/fipsmodule/curve25519/curve25519_s2n_bignum_asm.c @@ -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 diff --git a/crypto/fipsmodule/curve25519/internal.h b/crypto/fipsmodule/curve25519/internal.h index 0f1010d7b4..ebedea9c81 100644 --- a/crypto/fipsmodule/curve25519/internal.h +++ b/crypto/fipsmodule/curve25519/internal.h @@ -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 diff --git a/util/fipstools/acvp/acvptool/subprocess/eddsa.go b/util/fipstools/acvp/acvptool/subprocess/eddsa.go new file mode 100644 index 0000000000..2f8698b73f --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/eddsa.go @@ -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"` +} diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index 2f21c70723..dc60c3050a 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -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} diff --git a/util/fipstools/acvp/acvptool/test/expected/EDDSA.bz2 b/util/fipstools/acvp/acvptool/test/expected/EDDSA.bz2 new file mode 100644 index 0000000000..954e023b51 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/expected/EDDSA.bz2 differ diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index ee530fffa9..1ac72c0bef 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json @@ -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"} ] diff --git a/util/fipstools/acvp/acvptool/test/vectors/EDDSA-KeyGen.bz2 b/util/fipstools/acvp/acvptool/test/vectors/EDDSA-KeyGen.bz2 new file mode 100644 index 0000000000..66c8536078 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/EDDSA-KeyGen.bz2 differ diff --git a/util/fipstools/acvp/acvptool/test/vectors/EDDSA-SigGen.bz2 b/util/fipstools/acvp/acvptool/test/vectors/EDDSA-SigGen.bz2 new file mode 100644 index 0000000000..98199ace17 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/EDDSA-SigGen.bz2 differ diff --git a/util/fipstools/acvp/acvptool/test/vectors/EDDSA.bz2 b/util/fipstools/acvp/acvptool/test/vectors/EDDSA.bz2 new file mode 100644 index 0000000000..4d32631402 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/EDDSA.bz2 differ diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 61dcf0843f..31dfa2ac1f 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ #include "../../../../crypto/fipsmodule/ec/internal.h" #include "../../../../crypto/fipsmodule/hmac/internal.h" #include "../../../../crypto/fipsmodule/rand/internal.h" +#include "../../../../crypto/fipsmodule/curve25519/internal.h" #include "modulewrapper.h" @@ -1330,8 +1332,32 @@ static bool GetConfig(const Span args[], "revision": "FIPS203", "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"], "functions": ["encapsulation", "decapsulation"] - } - ])"; + },)" + R"({ + "algorithm": "EDDSA", + "mode": "keyGen", + "revision": "1.0", + "curve": ["ED-25519"] + },{ + "algorithm": "EDDSA", + "mode": "keyVer", + "revision": "1.0", + "curve": ["ED-25519"] + },{ + "algorithm": "EDDSA", + "mode": "sigGen", + "revision": "1.0", + "curve": ["ED-25519"], + "pure": true, + "preHash": false + },{ + "algorithm": "EDDSA", + "mode": "sigVer", + "revision": "1.0", + "curve": ["ED-25519"], + "pure": true, + "preHash": false + }])"; return write_reply({Span( reinterpret_cast(kConfig), sizeof(kConfig) - 1)}); } @@ -2967,6 +2993,66 @@ static bool ML_KEM_DECAP(const Span args[], {Span(shared_secret.data(), shared_secret_len)}); } +static bool ED25519KeyGen(const Span args[], + ReplyCallback write_reply) { + std::vector private_key(ED25519_PRIVATE_KEY_LEN); + std::vector public_key(ED25519_PUBLIC_KEY_LEN); + ::ED25519_keypair(public_key.data(), private_key.data()); + const Span seed(private_key.data(), ED25519_PRIVATE_KEY_SEED_LEN); + return write_reply({seed, Span(public_key)}); +} + +static bool ED25519KeyVer(const Span args[], + ReplyCallback write_reply) { + const Span public_key = args[0]; + + uint8_t reply[1] = {0}; + if (::ED25519_check_public_key(public_key.data())) { + reply[0] = 1; + } else { + ERR_clear_error(); + } + + return write_reply({Span(reply)}); +} + +static bool ED25519SigGen(const Span args[], + ReplyCallback write_reply) { + const Span seed = args[0]; + const Span message = args[1]; + + std::vector private_key(ED25519_PRIVATE_KEY_LEN); + std::vector public_key(ED25519_PUBLIC_KEY_LEN); + std::vector signature(ED25519_SIGNATURE_LEN); + + ::ED25519_keypair_from_seed(public_key.data(), private_key.data(), + seed.data()); + + if (!::ED25519_sign(signature.data(), message.data(), message.size(), + private_key.data())) { + return false; + } + + return write_reply({Span(signature)}); +} + +static bool ED25519SigVer(const Span args[], + ReplyCallback write_reply) { + const Span message = args[0]; + const Span public_key = args[1]; + const Span signature = args[2]; + + uint8_t reply[1] = {0}; + if (::ED25519_verify(message.data(), message.size(), signature.data(), + public_key.data())) { + reply[0] = 1; + } else { + ERR_clear_error(); + } + + return write_reply({Span(reply)}); +} + static struct { char name[kMaxNameLength + 1]; uint8_t num_expected_args; @@ -3210,7 +3296,10 @@ static struct { {"ML-KEM/ML-KEM-512/decap", 2, ML_KEM_DECAP}, {"ML-KEM/ML-KEM-768/decap", 2, ML_KEM_DECAP}, {"ML-KEM/ML-KEM-1024/decap", 2, ML_KEM_DECAP}, -}; + {"EDDSA/ED-25519/keyGen", 0, ED25519KeyGen}, + {"EDDSA/ED-25519/keyVer", 1, ED25519KeyVer}, + {"EDDSA/ED-25519/sigGen", 2, ED25519SigGen}, + {"EDDSA/ED-25519/sigVer", 3, ED25519SigVer}}; Handler FindHandler(Span> args) { const bssl::Span algorithm = args[0];