Skip to content

Commit

Permalink
ACVP test harness for ML-DSA (aws#2127)
Browse files Browse the repository at this point in the history
### Issues:
Resolves #CryptoAlg-2819

### Description of changes: 
Implements:
- https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html
-
https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files/ML-DSA-keyGen-FIPS204
-
https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files/ML-DSA-sigGen-FIPS204
-
https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files/ML-DSA-sigVer-FIPS204

### Additional Details
Added testing for ML-DSA configured with`deterministic = true`
`externalmu = true, false` `signatureInterface = internal` for
`parameterSet = ML-DSA-44, ML-DSA-65, ML-DSA-87`.

We add the [ML-DSA sigGen Test Case JSON
Schema](https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html#name-ml-dsa-siggen-test-case-jso):

```
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"`
	}
}
```
and [ML-DSA sigVer Test Case JSON
Schema](https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html#name-ml-dsa-sigver-test-case-jso):
```
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"`
	}
}
```




### Testing:
```
Running ACVP tests
2025/01/17 14:09:39 40 ACVP tests matched expectations
2025/01/17 14:09:40 1 ACVP tests matched expectations
2025/01/17 14:09:41 1 ACVP tests matched expectations
2025/01/17 14:09:42 1 ACVP tests matched expectations
2025/01/17 14:09:44 1 ACVP tests matched expectations
2025/01/17 14:09:46 1 ACVP tests matched expectations
2025/01/17 14:09:48 1 ACVP tests matched expectations
2025/01/17 14:09:50 1 ACVP tests matched expectations
2025/01/17 14:09:52 1 ACVP tests matched expectations
2025/01/17 14:10:09 1 ACVP tests matched expectations
2025/01/17 14:10:10 1 ACVP tests matched expectations
2025/01/17 14:10:11 1 ACVP tests matched expectations
2025/01/17 14:10:12 1 ACVP tests matched expectations
2025/01/17 14:10:12 1 ACVP tests matched expectations
```

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and the ISC license.
  • Loading branch information
jakemas authored and Chocka Chidambaram committed Jan 24, 2025
1 parent dc1d127 commit 2b3778d
Show file tree
Hide file tree
Showing 7 changed files with 487 additions and 5 deletions.
13 changes: 10 additions & 3 deletions crypto/ml_dsa/ml_dsa_ref/params.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
239 changes: 239 additions & 0 deletions util/fipstools/acvp/acvptool/subprocess/ml_dsa.go
Original file line number Diff line number Diff line change
@@ -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
}
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 @@ -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}

Expand Down
Binary file not shown.
3 changes: 2 additions & 1 deletion util/fipstools/acvp/acvptool/test/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
]
Binary file not shown.
Loading

0 comments on commit 2b3778d

Please sign in to comment.