From 0c77c940035b58fdc98b0c2f33f45a72b0cc2aea Mon Sep 17 00:00:00 2001 From: Jake Massimo Date: Wed, 18 Dec 2024 11:10:43 -0800 Subject: [PATCH] add acvp test framework for ml-dsa --- crypto/dilithium/ml_dsa.c | 6 +- crypto/dilithium/ml_dsa.h | 6 +- .../pqcrystals_dilithium_ref_common/params.h | 13 +- .../acvp/acvptool/subprocess/ml_dsa.go | 220 ++++++++++++++++++ .../acvp/acvptool/subprocess/subprocess.go | 1 + util/fipstools/acvp/acvptool/test/tests.json | 3 +- .../acvp/modulewrapper/modulewrapper.cc | 145 ++++++++++++ 7 files changed, 384 insertions(+), 10 deletions(-) create mode 100644 util/fipstools/acvp/acvptool/subprocess/ml_dsa.go diff --git a/crypto/dilithium/ml_dsa.c b/crypto/dilithium/ml_dsa.c index 1c80a1a524..33f77b4092 100644 --- a/crypto/dilithium/ml_dsa.c +++ b/crypto/dilithium/ml_dsa.c @@ -58,7 +58,7 @@ int ml_dsa_44_sign_internal(const uint8_t *private_key /* IN */, size_t message_len /* IN */, const uint8_t *pre /* IN */, size_t pre_len /* IN */, - uint8_t *rnd /* IN */) { + const uint8_t *rnd /* IN */) { ml_dsa_params params; ml_dsa_44_params_init(¶ms); return crypto_sign_signature_internal(¶ms, sig, sig_len, message, message_len, @@ -126,7 +126,7 @@ int ml_dsa_65_sign_internal(const uint8_t *private_key /* IN */, size_t message_len /* IN */, const uint8_t *pre /* IN */, size_t pre_len /* IN */, - uint8_t *rnd /* IN */) { + const uint8_t *rnd /* IN */) { ml_dsa_params params; ml_dsa_65_params_init(¶ms); return crypto_sign_signature_internal(¶ms, sig, sig_len, message, message_len, @@ -194,7 +194,7 @@ int ml_dsa_87_sign_internal(const uint8_t *private_key /* IN */, size_t message_len /* IN */, const uint8_t *pre /* IN */, size_t pre_len /* IN */, - uint8_t *rnd /* IN */) { + const uint8_t *rnd /* IN */) { ml_dsa_params params; ml_dsa_87_params_init(¶ms); return crypto_sign_signature_internal(¶ms, sig, sig_len, message, message_len, diff --git a/crypto/dilithium/ml_dsa.h b/crypto/dilithium/ml_dsa.h index 224fe3d27e..f6ee1b3ccf 100644 --- a/crypto/dilithium/ml_dsa.h +++ b/crypto/dilithium/ml_dsa.h @@ -46,7 +46,7 @@ OPENSSL_EXPORT int ml_dsa_44_sign_internal(const uint8_t *private_key, uint8_t *sig, size_t *sig_len, const uint8_t *message, size_t message_len, const uint8_t *pre, size_t pre_len, - uint8_t *rnd); + const uint8_t *rnd); OPENSSL_EXPORT int ml_dsa_44_verify(const uint8_t *public_key, const uint8_t *sig, size_t sig_len, @@ -74,7 +74,7 @@ OPENSSL_EXPORT int ml_dsa_65_sign_internal(const uint8_t *private_key, uint8_t *sig, size_t *sig_len, const uint8_t *message, size_t message_len, const uint8_t *pre, size_t pre_len, - uint8_t *rnd); + const uint8_t *rnd); OPENSSL_EXPORT int ml_dsa_65_verify(const uint8_t *public_key, const uint8_t *sig, size_t sig_len, @@ -102,7 +102,7 @@ OPENSSL_EXPORT int ml_dsa_87_sign_internal(const uint8_t *private_key, uint8_t *sig, size_t *sig_len, const uint8_t *message, size_t message_len, const uint8_t *pre, size_t pre_len, - uint8_t *rnd); + const uint8_t *rnd); OPENSSL_EXPORT int ml_dsa_87_verify(const uint8_t *public_key, const uint8_t *sig, size_t sig_len, diff --git a/crypto/dilithium/pqcrystals_dilithium_ref_common/params.h b/crypto/dilithium/pqcrystals_dilithium_ref_common/params.h index 2cdbaad391..e0e8f72118 100644 --- a/crypto/dilithium/pqcrystals_dilithium_ref_common/params.h +++ b/crypto/dilithium/pqcrystals_dilithium_ref_common/params.h @@ -1,6 +1,10 @@ #ifndef PARAMS_H #define PARAMS_H +#if defined(__cplusplus) +extern "C" { +#endif + // The only defined parameters are those that don't depend // on the parameter set. All other parameters are specified // in ml_dsa_params structure that is unique for each parameter @@ -45,8 +49,11 @@ typedef struct { #define DILITHIUM_POLY_UNIFORM_ETA_NBLOCKS_MAX ((227 + SHAKE256_RATE - 1)/SHAKE256_RATE) #define DILITHIUM_POLYZ_PACKEDBYTES_MAX (576) -void ml_dsa_44_params_init(ml_dsa_params *params); -void ml_dsa_65_params_init(ml_dsa_params *params); -void ml_dsa_87_params_init(ml_dsa_params *params); +OPENSSL_EXPORT void ml_dsa_44_params_init(ml_dsa_params *params); +OPENSSL_EXPORT void ml_dsa_65_params_init(ml_dsa_params *params); +OPENSSL_EXPORT void ml_dsa_87_params_init(ml_dsa_params *params); +#if defined(__cplusplus) +} +#endif #endif diff --git a/util/fipstools/acvp/acvptool/subprocess/ml_dsa.go b/util/fipstools/acvp/acvptool/subprocess/ml_dsa.go new file mode 100644 index 0000000000..8a91991519 --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/ml_dsa.go @@ -0,0 +1,220 @@ +// 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" +) + +type mlDsa struct{} + +func (*mlDsa) 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 + } + + switch { + case strings.EqualFold(vs.Mode, "keyGen"): + return processMlDsaKeyGen(vs.TestGroups, m) + case strings.EqualFold(vs.Mode, "sigGen"): + return processMlDsaSigGen(vs.TestGroups, m) + case strings.EqualFold(vs.Mode, "sigVer"): + return processMlDsaSigVer(vs.TestGroups, m) + } + + return nil, fmt.Errorf("unknown ML-DSA mode: %v", vs.Mode) +} + +type mlDsaKeyGenTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + ParameterSet string `json:"parameterSet"` + Tests []struct { + ID uint64 `json:"tcId"` + SEED hexEncodedByteString `json:"seed"` + } +} + +type mlDsaKeyGenTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlDsaKeyGenTestCaseResponse `json:"tests"` +} + +type mlDsaKeyGenTestCaseResponse struct { + ID uint64 `json:"tcId"` + PK hexEncodedByteString `json:"pk"` + SK hexEncodedByteString `json:"sk` +} + +func processMlDsaKeyGen(vectors json.RawMessage, m Transactable) (interface{}, error) { + var groups []mlDsaKeyGenTestGroup + + if err := json.Unmarshal(vectors, &groups); err != nil { + return nil, err + } + + var responses []mlDsaKeyGenTestGroupResponse + + for _, group := range groups { + if !strings.EqualFold(group.Type, "AFT") { + return nil, fmt.Errorf("unsupported keyGen test type: %v", group.Type) + } + + response := mlDsaKeyGenTestGroupResponse{ + ID: group.ID, + } + + for _, test := range group.Tests { + results, err := m.Transact("ML-DSA/"+group.ParameterSet+"/keyGen", 1, test.SEED) + if err != nil { + return nil, err + } + + pk := results[0] + sk := results[1] + + response.Tests = append(response.Tests, mlDsaKeyGenTestCaseResponse{ + ID: test.ID, + PK: pk, + SK: sk, + }) + } + + responses = append(responses, response) + } + + return responses, nil +} + +type mlDsaSigGenTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + ParameterSet string `json:"parameterSet"` + Deterministic bool `json:"deterministic"` + Tests []struct { + ID uint64 `json:"tcId"` + MESSAGE hexEncodedByteString `json:"message"` + SK hexEncodedByteString `json:"sk"` + RND hexEncodedByteString `json:"rnd"` + } +} + + +type mlDsaSigGenTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlDsaSigGenTestCaseResponse `json:"tests"` +} + +type mlDsaSigGenTestCaseResponse struct { + ID uint64 `json:"tcId"` + SIGNATURE hexEncodedByteString `json:"signature"` +} + +func processMlDsaSigGen(vectors json.RawMessage, m Transactable) (interface{}, error) { + var groups []mlDsaSigGenTestGroup + + if err := json.Unmarshal(vectors, &groups); err != nil { + return nil, err + } + + var responses []mlDsaSigGenTestGroupResponse + + for _, group := range groups { + if !strings.EqualFold(group.Type, "AFT") { + return nil, fmt.Errorf("unsupported sigGen test type: %v", group.Type) + } + + response := mlDsaSigGenTestGroupResponse{ + ID: group.ID, + } + + for _, test := range group.Tests { + results, err := m.Transact("ML-DSA/"+group.ParameterSet+"/sigGen", 3, test.SK, test.MESSAGE, test.RND) + if err != nil { + return nil, err + } + + signature := results[0] + + response.Tests = append(response.Tests, mlDsaSigGenTestCaseResponse{ + ID: test.ID, + SIGNATURE: signature, + }) + } + + responses = append(responses, response) + } + return responses, nil +} + +type mlDsaSigVerTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + ParameterSet string `json:"parameterSet"` + PK hexEncodedByteString `json:"pk"` + Tests []struct { + ID uint64 `json:"tcId"` + MESSAGE hexEncodedByteString `json:"message"` + SIGNATURE hexEncodedByteString `json:"signature"` + } +} + +type mlDsaSigVerTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlDsaSigVerTestCaseResponse `json:"tests"` +} + +type mlDsaSigVerTestCaseResponse struct { + ID uint64 `json:"tcId"` + TESTPASSED *bool `json:"testPassed"` +} + + +func processMlDsaSigVer(vectors json.RawMessage, m Transactable) (interface{}, error) { + var groups []mlDsaSigVerTestGroup + + if err := json.Unmarshal(vectors, &groups); err != nil { + return nil, err + } + + var responses []mlDsaSigVerTestGroupResponse + + for _, group := range groups { + if !strings.EqualFold(group.Type, "AFT") { + return nil, fmt.Errorf("unsupported sigVer test type: %v", group.Type) + } + + response := mlDsaSigVerTestGroupResponse{ + ID: group.ID, + } + + for _, test := range group.Tests { + results, err := m.Transact("ML-DSA/"+group.ParameterSet+"/sigVer", 3, test.SIGNATURE, group.PK, test.MESSAGE) + 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, mlDsaSigVerTestCaseResponse{ + ID: test.ID, + TESTPASSED: passed, + }) + } + + responses = append(responses, response) + } + return responses, nil +} diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index dc60c3050a..4825a8218c 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -155,6 +155,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess "PBKDF": &pbkdf{}, "ML-KEM": &mlKem{}, "EDDSA": &eddsa{}, + "ML-DSA": &mlDsa{}, } 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/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index a011e2b388..d344f105d5 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json @@ -37,5 +37,6 @@ {"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"} +{"Wrapper": "modulewrapper", "In": "vectors/EDDSA-SigGen.bz2"}, +{"Wrapper": "modulewrapper", "In": "vectors/ML-DSA.bz2", "Out": "expected/ML-DSA.bz2"} ] diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 5328922589..ad0896c450 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -57,6 +57,8 @@ #include "../../../../crypto/fipsmodule/hmac/internal.h" #include "../../../../crypto/fipsmodule/rand/internal.h" #include "../../../../crypto/fipsmodule/curve25519/internal.h" +#include "../../../../crypto/dilithium/ml_dsa.h" +#include "../../../../crypto/dilithium/pqcrystals_dilithium_ref_common/params.h" #include "modulewrapper.h" @@ -1352,6 +1354,26 @@ static bool GetConfig(const Span args[], "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"], "functions": ["encapsulation", "decapsulation"] },)" + R"({ + "algorithm": "ML-DSA", + "mode": "keyGen", + "revision": "FIPS204", + "parameterSets": ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"] + }, + { + "algorithm": "ML-DSA", + "mode": "sigGen", + "revision": "FIPS204", + "parameterSets": ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"], + "deterministic": [false], + "messageLength": [{"min": 8, "max": 65536", "increment": 8}] + } + { + "algorithm": "ML-DSA", + "mode": "sigVer", + "revision": "FIPS204", + "parameterSets": ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"], + },)" R"({ "algorithm": "EDDSA", "mode": "keyGen", @@ -3012,6 +3034,120 @@ static bool ML_KEM_DECAP(const Span args[], {Span(shared_secret.data(), shared_secret_len)}); } +template +static bool ML_DSA_KEYGEN(const Span args[], + ReplyCallback write_reply) { + const Span seed = args[0]; + + //init params of the correct size based on provided nid + ml_dsa_params params; + if (nid == NID_MLDSA44) { + ml_dsa_44_params_init(¶ms); + } + else if (nid == NID_MLDSA65) { + ml_dsa_65_params_init(¶ms); + } + else if (nid == NID_MLDSA87) { + ml_dsa_87_params_init(¶ms); + } + + // create public and private key buffers + std::vector public_key(params.public_key_bytes); + std::vector private_key(params.secret_key_bytes); + + // generate the keys + if (nid == NID_MLDSA44 ) { + if (!ml_dsa_44_keypair_internal(public_key.data(), private_key.data(), seed.data())) { + return false; + } + } + else if (nid == NID_MLDSA65) { + if (!ml_dsa_65_keypair_internal(public_key.data(), private_key.data(), seed.data())) { + return false; + } + } + else if (nid == NID_MLDSA87) { + if (!ml_dsa_87_keypair_internal(public_key.data(), private_key.data(), seed.data())) { + return false; + } + } + return write_reply({Span(public_key.data(), public_key.size()), + Span(private_key.data(), private_key.size())}); +} + +template +static bool ML_DSA_SIGGEN(const Span args[], + ReplyCallback write_reply) { + const Span sk = args[0]; + const Span msg = args[1]; + const Span rnd = args[2]; + + ml_dsa_params params; + if (nid == NID_MLDSA44) { + ml_dsa_44_params_init(¶ms); + } + else if (nid == NID_MLDSA65) { + ml_dsa_65_params_init(¶ms); + } + else if (nid == NID_MLDSA87) { + ml_dsa_87_params_init(¶ms); + } + + size_t signature_len = params.bytes; + std::vector signature(signature_len); + + // generate the keys + if (nid == NID_MLDSA44) { + if (!ml_dsa_44_sign_internal(sk.data(), signature.data(), &signature_len, + msg.data(), msg.size(), nullptr, 0, rnd.data())) { + return false; + } + } + else if (nid == NID_MLDSA65) { + if (!ml_dsa_65_sign_internal(sk.data(), signature.data(), &signature_len, + msg.data(), msg.size(), nullptr, 0, rnd.data())) { + return false; + } + } + else if (nid == NID_MLDSA87) { + if (!ml_dsa_87_sign_internal(sk.data(), signature.data(), &signature_len, + msg.data(), msg.size(), nullptr, 0, rnd.data())) { + return false; + } + } + return write_reply({Span(signature)}); +} + +template +static bool ML_DSA_SIGVER(const Span args[], + ReplyCallback write_reply) { + const Span sig = args[0]; + const Span pk = args[1]; + const Span msg = args[2]; + + uint8_t reply[1] = {0}; + + if (nid == NID_MLDSA44) { + if (ml_dsa_44_verify_internal(pk.data(), sig.data(), sig.size(), msg.data(), + msg.size(), nullptr, 0)) { + reply[0] = 1; + } + } + else if (nid == NID_MLDSA65) { + if (!ml_dsa_65_verify_internal(pk.data(), sig.data(), sig.size(), msg.data(), + msg.size(), nullptr, 0)) { + reply[0] = 1; + } + } + else if (nid == NID_MLDSA87) { + if (!ml_dsa_87_verify_internal(pk.data(), sig.data(), sig.size(), msg.data(), + msg.size(), nullptr, 0)) { + reply[0] = 1; + } + } + return write_reply({Span(reply)}); +} + static bool ED25519KeyGen(const Span args[], ReplyCallback write_reply) { std::vector private_key(ED25519_PRIVATE_KEY_LEN); @@ -3317,6 +3453,15 @@ 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}, + {"ML-DSA/ML-DSA-44/keyGen", 1, ML_DSA_KEYGEN}, + {"ML-DSA/ML-DSA-65/keyGen", 1, ML_DSA_KEYGEN}, + {"ML-DSA/ML-DSA-87/keyGen", 1, ML_DSA_KEYGEN}, + {"ML-DSA/ML-DSA-44/sigGen", 3, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-65/sigGen", 3, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-87/sigGen", 3, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-44/sigVer", 3, ML_DSA_SIGVER}, + {"ML-DSA/ML-DSA-65/sigVer", 3, ML_DSA_SIGVER}, + {"ML-DSA/ML-DSA-87/sigVer", 3, ML_DSA_SIGVER}, {"EDDSA/ED-25519/keyGen", 0, ED25519KeyGen}, {"EDDSA/ED-25519/keyVer", 1, ED25519KeyVer}, {"EDDSA/ED-25519/sigGen", 2, ED25519SigGen},