diff --git a/crypto/ml_dsa/ml_dsa_ref/params.h b/crypto/ml_dsa/ml_dsa_ref/params.h index 633de6accd..8fe92013af 100644 --- a/crypto/ml_dsa/ml_dsa_ref/params.h +++ b/crypto/ml_dsa/ml_dsa_ref/params.h @@ -1,6 +1,10 @@ #ifndef ML_DSA_PARAMS_H #define ML_DSA_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 @@ -44,8 +48,11 @@ typedef struct { #define ML_DSA_POLY_UNIFORM_ETA_NBLOCKS_MAX ((227 + SHAKE256_BLOCKSIZE - 1)/SHAKE256_BLOCKSIZE) #define ML_DSA_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..fa7e7bc553 --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/ml_dsa.go @@ -0,0 +1,239 @@ +// 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", 2, 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"` + SignatureInterface string `json:"signatureInterface"` + ExternalMu bool `json:"externalMu` + Tests []struct { + ID uint64 `json:"tcId"` + Message hexEncodedByteString `json:"message"` + MU hexEncodedByteString `json:"mu"` + SK hexEncodedByteString `json:"sk"` + RND hexEncodedByteString `json:"rnd"` + Context hexEncodedByteString `json:"context"` + HashAlg string `json:"hashAlg"` + } +} + +type mlDsaSigGenTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlDsaSigGenTestCaseResponse `json:"tests"` +} + +type mlDsaSigGenTestCaseResponse struct { + ID uint64 `json:"tcId"` + Signature hexEncodedByteString `json:"signature"` +} + +// Convert boolean to byte slice (using 1 for true, 0 for false) +func boolToBytes(b bool) []byte { + if b { + return []byte{1} + } + return []byte{0} +} + +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", + 1, test.SK, test.Message, test.MU, test.RND, boolToBytes(group.ExternalMu)) + 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"` + Deterministic bool `json:"deterministic"` + SignatureInterface string `json:"signatureInterface"` + ExternalMu bool `json:"externalMu` + Tests []struct { + ID uint64 `json:"tcId"` + PK hexEncodedByteString `json:"pk"` + Message hexEncodedByteString `json:"message"` + MU hexEncodedByteString `json:"mu"` + Signature hexEncodedByteString `json:"signature"` + Context hexEncodedByteString `json:"context"` + HashAlg string `json:"hashAlg"` + } +} + +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", 1, + test.Signature, test.PK, test.Message, test.MU, boolToBytes(group.ExternalMu)) + 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/expected/ML-DSA.bz2 b/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2 new file mode 100644 index 0000000000..9f81f11c4d Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/expected/ML-DSA.bz2 differ 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/acvptool/test/vectors/ML-DSA.bz2 b/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2 new file mode 100644 index 0000000000..e9020f994b Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/ML-DSA.bz2 differ diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 5328922589..e558f633a3 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/ml_dsa/ml_dsa.h" +#include "../../../../crypto/ml_dsa/ml_dsa_ref/params.h" #include "modulewrapper.h" @@ -1376,6 +1378,64 @@ static bool GetConfig(const Span args[], "curve": ["ED-25519"], "pure": true, "preHash": false + },)" + 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", + "capabilities": [ + { + "parameterSets": [ + "ML-DSA-44", + "ML-DSA-65", + "ML-DSA-87" + ], + "messageLength": [ + { + "min": 8, + "max": 65536, + "increment": 8 + } + ] + } + ], + "deterministic": [false], + "externalMu": [ + true, + false + ], + "signatureInterfaces": ["internal"] + },{ + "algorithm": "ML-DSA", + "mode": "sigVer", + "revision": "FIPS204", + "capabilities": [ + { + "parameterSets": [ + "ML-DSA-44", + "ML-DSA-65", + "ML-DSA-87" + ], + "messageLength": [ + { + "min": 8, + "max": 65536, + "increment": 8 + } + ] + } + ], + "deterministic": [false], + "externalMu": [ + true, + false + ], + "signatureInterfaces": ["internal"] }])"; return write_reply({Span( reinterpret_cast(kConfig), sizeof(kConfig) - 1)}); @@ -3072,6 +3132,171 @@ static bool ED25519SigVer(const Span args[], return write_reply({Span(reply)}); } +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 mu = args[2]; + const Span rnd = args[3]; + const Span extmu = args[4]; + + 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 signatures raw sign mode + if (extmu.data()[0] == 0) { + 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; + } + } + } + // generate the signatures digest sign mode (externalmu) + else { + if (nid == NID_MLDSA44) { + if (!ml_dsa_extmu_44_sign_internal(sk.data(), signature.data(), &signature_len, + mu.data(), mu.size(), nullptr, 0, rnd.data())) { + return false; + } + } + else if (nid == NID_MLDSA65) { + if (!ml_dsa_extmu_65_sign_internal(sk.data(), signature.data(), &signature_len, + mu.data(), mu.size(), nullptr, 0, rnd.data())) { + return false; + } + } + else if (nid == NID_MLDSA87) { + if (!ml_dsa_extmu_87_sign_internal(sk.data(), signature.data(), &signature_len, + mu.data(), mu.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]; + const Span mu = args[3]; + const Span extmu = args[4]; + + uint8_t reply[1] = {0}; + + // verify the signatures raw sign mode + if (extmu.data()[0] == 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; + } + } + } + // verify the signatures digest sign mode (externalmu) + else{ + if (nid == NID_MLDSA44) { + if (ml_dsa_extmu_44_verify_internal(pk.data(), sig.data(), sig.size(), mu.data(), + mu.size(), nullptr, 0)) { + reply[0] = 1; + } + } + else if (nid == NID_MLDSA65) { + if (ml_dsa_extmu_65_verify_internal(pk.data(), sig.data(), sig.size(), mu.data(), + mu.size(), nullptr, 0)) { + reply[0] = 1; + } + } + else if (nid == NID_MLDSA87) { + if (ml_dsa_extmu_87_verify_internal(pk.data(), sig.data(), sig.size(), mu.data(), + mu.size(), nullptr, 0)) { + reply[0] = 1; + } + } + } + return write_reply({Span(reply)}); +} + static struct { char name[kMaxNameLength + 1]; uint8_t num_expected_args; @@ -3320,7 +3545,16 @@ static struct { {"EDDSA/ED-25519/keyGen", 0, ED25519KeyGen}, {"EDDSA/ED-25519/keyVer", 1, ED25519KeyVer}, {"EDDSA/ED-25519/sigGen", 2, ED25519SigGen}, - {"EDDSA/ED-25519/sigVer", 3, ED25519SigVer}}; + {"EDDSA/ED-25519/sigVer", 3, ED25519SigVer}, + {"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", 5, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-65/sigGen", 5, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-87/sigGen", 5, ML_DSA_SIGGEN}, + {"ML-DSA/ML-DSA-44/sigVer", 5, ML_DSA_SIGVER}, + {"ML-DSA/ML-DSA-65/sigVer", 5, ML_DSA_SIGVER}, + {"ML-DSA/ML-DSA-87/sigVer", 5, ML_DSA_SIGVER}}; Handler FindHandler(Span> args) { const bssl::Span algorithm = args[0];