diff --git a/util/fipstools/acvp/acvptool/subprocess/ml_kem.go b/util/fipstools/acvp/acvptool/subprocess/ml_kem.go new file mode 100644 index 0000000000..59d5c4d75f --- /dev/null +++ b/util/fipstools/acvp/acvptool/subprocess/ml_kem.go @@ -0,0 +1,194 @@ +// 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 mlKem struct{} + +func (*mlKem) 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 processMlKemKeyGen(vs.TestGroups, m) + case strings.EqualFold(vs.Mode, "encapDecap"): + return processMlKemEncapDecap(vs.TestGroups, m) + } + + return nil, fmt.Errorf("unknown ML-KEM mode: %v", vs.Mode) +} + +type mlKemKeyGenTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + ParameterSet string `json:"parameterSet"` + Tests []struct { + ID uint64 `json:"tcId"` + D hexEncodedByteString `json:"d"` + Z hexEncodedByteString `json:"z"` + } +} + +type mlKemKeyGenTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlKemKeyGenTestCaseResponse `json:"tests"` +} + +type mlKemKeyGenTestCaseResponse struct { + ID uint64 `json:"tcId"` + EK hexEncodedByteString `json:"ek"` + DK hexEncodedByteString `json:"dk"` +} + +func processMlKemKeyGen(vectors json.RawMessage, m Transactable) (interface{}, error) { + var groups []mlKemKeyGenTestGroup + + if err := json.Unmarshal(vectors, &groups); err != nil { + return nil, err + } + + var responses []mlKemKeyGenTestGroupResponse + + for _, group := range groups { + if !strings.EqualFold(group.Type, "AFT") { + return nil, fmt.Errorf("unsupported keyGen test type: %v", group.Type) + } + + response := mlKemKeyGenTestGroupResponse{ + ID: group.ID, + } + + for _, test := range group.Tests { + results, err := m.Transact("ML-KEM/"+group.ParameterSet+"/keyGen", 2, test.D, test.Z) + if err != nil { + return nil, err + } + + ek := results[0] + dk := results[1] + + response.Tests = append(response.Tests, mlKemKeyGenTestCaseResponse{ + ID: test.ID, + EK: ek, + DK: dk, + }) + } + + responses = append(responses, response) + } + + return responses, nil +} + +type mlKemEncapDecapTestGroup struct { + ID uint64 `json:"tgId"` + Type string `json:"testType"` + ParameterSet string `json:"parameterSet"` + Function string `json:"function"` + DK hexEncodedByteString `json:"dk"` + Tests []struct { + ID uint64 `json:"tcId"` + EK hexEncodedByteString `json:"ek"` + M hexEncodedByteString `json:"m"` + C hexEncodedByteString `json:"c"` + } +} + +type mlKemEncDecapTestGroupResponse struct { + ID uint64 `json:"tgId"` + Tests []mlKemEncDecapTestCaseResponse `json:"tests"` +} + +type mlKemEncDecapTestCaseResponse struct { + ID uint64 `json:"tcId"` + C hexEncodedByteString `json:"c,omitempty"` + K hexEncodedByteString `json:"k,omitempty"` +} + +func processMlKemEncapDecap(vectors json.RawMessage, m Transactable) (interface{}, error) { + var groups []mlKemEncapDecapTestGroup + + if err := json.Unmarshal(vectors, &groups); err != nil { + return nil, err + } + + var responses []mlKemEncDecapTestGroupResponse + + for _, group := range groups { + if (strings.EqualFold(group.Function, "encapsulation") && !strings.EqualFold(group.Type, "AFT")) || + (strings.EqualFold(group.Function, "decapsulation") && !strings.EqualFold(group.Type, "VAL")) { + return nil, fmt.Errorf("unsupported encapDecap function and test group type pair: (%v, %v)", group.Function, group.Type) + } + + response := mlKemEncDecapTestGroupResponse{ + ID: group.ID, + } + + for _, test := range group.Tests { + var ( + err error + testResponse mlKemEncDecapTestCaseResponse + ) + + switch { + case strings.EqualFold(group.Function, "encapsulation"): + testResponse, err = processMlKemEncapTestCase(test.ID, group.ParameterSet, test.EK, test.M, m) + case strings.EqualFold(group.Function, "decapsulation"): + testResponse, err = processMlKemDecapTestCase(test.ID, group.ParameterSet, group.DK, test.C, m) + default: + return nil, fmt.Errorf("unknown encDecap function: %v", group.Function) + } + if err != nil { + return nil, err + } + + response.Tests = append(response.Tests, testResponse) + } + + responses = append(responses, response) + } + return responses, nil +} + +func processMlKemEncapTestCase(id uint64, algorithm string, ek []byte, m []byte, t Transactable) (mlKemEncDecapTestCaseResponse, error) { + results, err := t.Transact("ML-KEM/"+algorithm+"/encap", 2, ek, m) + if err != nil { + return mlKemEncDecapTestCaseResponse{}, err + } + + c := results[0] + k := results[1] + + return mlKemEncDecapTestCaseResponse{ + ID: id, + C: c, + K: k, + }, nil +} + +func processMlKemDecapTestCase(id uint64, algorithm string, dk []byte, c []byte, t Transactable) (mlKemEncDecapTestCaseResponse, error) { + results, err := t.Transact("ML-KEM/"+algorithm+"/decap", 1, dk, c) + if err != nil { + return mlKemEncDecapTestCaseResponse{}, err + } + + k := results[0] + + return mlKemEncDecapTestCaseResponse{ + ID: id, + K: k, + }, nil +} diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go index b655b4a4ec..2f21c70723 100644 --- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go +++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go @@ -153,6 +153,7 @@ func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess "KAS-ECC-SSC": &kas{}, "KAS-FFC-SSC": &kasDH{}, "PBKDF": &pbkdf{}, + "ML-KEM": &mlKem{}, } 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-KEM.bz2 b/util/fipstools/acvp/acvptool/test/expected/ML-KEM.bz2 new file mode 100644 index 0000000000..a1c8378cab Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/expected/ML-KEM.bz2 differ diff --git a/util/fipstools/acvp/acvptool/test/tests.json b/util/fipstools/acvp/acvptool/test/tests.json index 6aa28a1d70..c8f48bf2f9 100644 --- a/util/fipstools/acvp/acvptool/test/tests.json +++ b/util/fipstools/acvp/acvptool/test/tests.json @@ -31,5 +31,6 @@ {"Wrapper": "modulewrapper", "In": "vectors/TLS-1.2-KDF.bz2", "Out": "expected/TLS-1.2-KDF.bz2"}, {"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/KDA-OneStep.bz2", "Out": "expected/KDA-OneStep.bz2"}, +{"Wrapper": "modulewrapper", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"} ] diff --git a/util/fipstools/acvp/acvptool/test/vectors/ML-KEM.bz2 b/util/fipstools/acvp/acvptool/test/vectors/ML-KEM.bz2 new file mode 100644 index 0000000000..09da326091 Binary files /dev/null and b/util/fipstools/acvp/acvptool/test/vectors/ML-KEM.bz2 differ diff --git a/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/util/fipstools/acvp/modulewrapper/modulewrapper.cc index 59d72e5bdb..4a8c4c5e31 100644 --- a/util/fipstools/acvp/modulewrapper/modulewrapper.cc +++ b/util/fipstools/acvp/modulewrapper/modulewrapper.cc @@ -12,11 +12,12 @@ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include #include -#include +#include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -732,7 +734,7 @@ static bool GetConfig(const Span args[], }] }] },)" - R"({ + R"({ "sigType": "pss", "properties": [{ "modulo": 2048, @@ -988,7 +990,7 @@ static bool GetConfig(const Span args[], }] }] },)" - R"({ + R"({ "sigType": "pss", "properties": [{ "modulo": 2048, @@ -1307,6 +1309,19 @@ static bool GetConfig(const Span args[], "encoding": ["concatenation"], "z": [{"min": 224, "max": 8192, "increment": 8}], "l": 2048 + },)" + R"({ + "algorithm": "ML-KEM", + "mode": "keyGen", + "revision": "FIPS203", + "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"] + }, + { + "algorithm": "ML-KEM", + "mode": "encapDecap", + "revision": "FIPS203", + "parameterSets": ["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"], + "functions": ["encapsulation", "decapsulation"] } ])"; return write_reply({Span( @@ -2830,6 +2845,112 @@ static bool KBKDF_CTR_HMAC(const Span args[], return write_reply({Span(out)}); } +template +static bool ML_KEM_KEYGEN(const Span args[], + ReplyCallback write_reply) { + const Span d = args[0]; + const Span z = args[1]; + + std::vector seed(d.size() + z.size()); + std::memcpy(seed.data(), d.data(), d.size()); + std::memcpy(seed.data() + d.size(), z.data(), z.size()); + + EVP_PKEY *raw = NULL; + size_t seed_len = 0; + + bssl::UniquePtr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_KEM, nullptr)); + if (!EVP_PKEY_CTX_kem_set_params(ctx.get(), nid) || + !EVP_PKEY_keygen_init(ctx.get()) || + !EVP_PKEY_keygen_deterministic(ctx.get(), &raw, NULL, &seed_len) || + seed_len != seed.size() || + !EVP_PKEY_keygen_deterministic(ctx.get(), &raw, seed.data(), &seed_len)) { + return false; + } + bssl::UniquePtr pkey(raw); + + size_t decaps_key_size = 0; + size_t encaps_key_size = 0; + + if (!EVP_PKEY_get_raw_private_key(pkey.get(), nullptr, &decaps_key_size) || + !EVP_PKEY_get_raw_public_key(pkey.get(), nullptr, &encaps_key_size)) { + return false; + } + + std::vector decaps_key(decaps_key_size); + std::vector encaps_key(encaps_key_size); + + if (!EVP_PKEY_get_raw_private_key(pkey.get(), decaps_key.data(), + &decaps_key_size) || + !EVP_PKEY_get_raw_public_key(pkey.get(), encaps_key.data(), + &encaps_key_size)) { + return false; + } + + return write_reply({Span(encaps_key.data(), encaps_key_size), + Span(decaps_key.data(), decaps_key_size)}); +} + +template +static bool ML_KEM_ENCAP(const Span args[], + ReplyCallback write_reply) { + const Span ek = args[0]; + const Span m = args[1]; + + bssl::UniquePtr pkey( + EVP_PKEY_kem_new_raw_public_key(nid, ek.data(), ek.size())); + bssl::UniquePtr ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + + size_t ciphertext_len = 0; + size_t shared_secret_len = 0; + size_t seed_len = 0; + if (!EVP_PKEY_encapsulate_deterministic(ctx.get(), nullptr, &ciphertext_len, + nullptr, &shared_secret_len, nullptr, + &seed_len) || + seed_len != m.size()) { + return false; + } + + std::vector ciphertext(ciphertext_len); + std::vector shared_secret(shared_secret_len); + + if (!EVP_PKEY_encapsulate_deterministic( + ctx.get(), ciphertext.data(), &ciphertext_len, shared_secret.data(), + &shared_secret_len, m.data(), &seed_len)) { + return false; + } + + return write_reply( + {Span(ciphertext.data(), ciphertext_len), + Span(shared_secret.data(), shared_secret_len)}); +} + +template +static bool ML_KEM_DECAP(const Span args[], + ReplyCallback write_reply) { + const Span dk = args[0]; + const Span c = args[1]; + + bssl::UniquePtr pkey( + EVP_PKEY_kem_new_raw_secret_key(nid, dk.data(), dk.size())); + bssl::UniquePtr ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + + size_t shared_secret_len = 0; + if (!EVP_PKEY_decapsulate(ctx.get(), nullptr, &shared_secret_len, c.data(), + c.size())) { + return false; + } + + std::vector shared_secret(shared_secret_len); + + if (!EVP_PKEY_decapsulate(ctx.get(), shared_secret.data(), &shared_secret_len, + c.data(), c.size())) { + return false; + } + + return write_reply( + {Span(shared_secret.data(), shared_secret_len)}); +} + static struct { char name[kMaxNameLength + 1]; uint8_t num_expected_args; @@ -3064,6 +3185,15 @@ static struct { {"KDF/Counter/HMAC-SHA2-512", 3, KBKDF_CTR_HMAC}, {"KDF/Counter/HMAC-SHA2-512/224", 3, KBKDF_CTR_HMAC}, {"KDF/Counter/HMAC-SHA2-512/256", 3, KBKDF_CTR_HMAC}, + {"ML-KEM/ML-KEM-512/keyGen", 2, ML_KEM_KEYGEN}, + {"ML-KEM/ML-KEM-768/keyGen", 2, ML_KEM_KEYGEN}, + {"ML-KEM/ML-KEM-1024/keyGen", 2, ML_KEM_KEYGEN}, + {"ML-KEM/ML-KEM-512/encap", 2, ML_KEM_ENCAP}, + {"ML-KEM/ML-KEM-768/encap", 2, ML_KEM_ENCAP}, + {"ML-KEM/ML-KEM-1024/encap", 2, ML_KEM_ENCAP}, + {"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}, }; Handler FindHandler(Span> args) {