Skip to content

Commit d6fa7a3

Browse files
committed
[cryptotest] Add X25519 ACVP host harness
This harness reads the ACVP HJSON file, sends the test vector to the device and writes the response into a JSON file that can be submitted to the ACVP server for validation. Signed-off-by: Pascal Nasahl <nasahlpa@lowrisc.org> (cherry picked from commit 81739c5)
1 parent 371c99d commit d6fa7a3

6 files changed

Lines changed: 328 additions & 1 deletion

File tree

sw/device/tests/crypto/cryptotest/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,16 @@ cryptotest(
264264
test_vectors = X25519_TESTVECTOR_TARGETS,
265265
)
266266

267+
cryptotest(
268+
name = "x25519_acvp",
269+
slow_test = True,
270+
test_args = "--input=\"$(rootpath //sw/host/cryptotest/testvectors/acvp:x25519_vectors.hjson)\"",
271+
test_harness = "//sw/host/tests/crypto/acvp:harness",
272+
test_vectors = [
273+
"//sw/host/cryptotest/testvectors/acvp:x25519_vectors.hjson",
274+
],
275+
)
276+
267277
RSA_TESTVECTOR_TARGETS = [
268278
"//sw/host/cryptotest/testvectors/data:wycheproof_rsa_pkcs1_1.5_2048_{}.json".format(hash)
269279
for hash in [
@@ -794,6 +804,7 @@ test_suite(
794804
":shake128_kat",
795805
":shake256_kat",
796806
":sphincsplus_kat",
807+
":x25519_acvp",
797808
":x25519_kat",
798809
],
799810
)

sw/host/cryptotest/testvectors/acvp/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ exports_files([
1313
"hmac_sha256_vectors.json",
1414
"rsa_expected.json",
1515
"rsa_vectors.json",
16+
"x25519_vectors.hjson",
1617
])
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
//
3+
// Source: NIST ACVP-Server (https://github.com/usnistgov/ACVP-Server)
4+
// NIST License:
5+
// NIST-developed software is provided by NIST as a public service. You may
6+
// use, copy, and distribute copies of the software in any medium, provided
7+
// that you keep intact this entire notice. You may improve, modify, and create
8+
// derivative works of the software or any portion of the software, and you may
9+
// copy and distribute such modifications or works. Modified works should carry
10+
// a notice stating that you changed the software and should note the date and
11+
// nature of any such change. Please explicitly acknowledge the National
12+
// Institute of Standards and Technology as the source of the software.
13+
//
14+
// NIST-developed software is expressly provided "AS IS." NIST MAKES NO
15+
// WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION OF
16+
// LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST
18+
// NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE
19+
// UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST
20+
// DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE
21+
// SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE
22+
// CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
23+
//
24+
// You are solely responsible for determining the appropriateness of using and
25+
// distributing the software and you assume all risks associated with its use,
26+
// including but not limited to the risks and costs of program errors,
27+
// compliance with applicable laws, damage to or loss of data, programs or
28+
// equipment, and the unavailability or interruption of operation. This
29+
// software is not intended to be used in any situation where a failure could
30+
// cause risk of injury or damage to property. The software developed by NIST
31+
// employees is not subject to copyright protection within the United States.
32+
//
33+
// Based on:
34+
// https://github.com/usnistgov/ACVP-Server/blob/master/gen-val/json-files/XECDH-SSC-RFC7748/prompt.json
35+
// Modifications:
36+
// This file was modified on 21.05.2026 as following:
37+
// - Removed test group 2.
38+
// - Wrapped in an array for compatibility with the ACVP harness.
39+
[{
40+
"vsId": 0,
41+
"algorithm": "XECDH",
42+
"mode": "SSC",
43+
"revision": "RFC7748",
44+
"isSample": true,
45+
"testGroups": [
46+
{
47+
"tgId": 1,
48+
"testType": "AFT",
49+
"curve": "Curve25519",
50+
"tests": [
51+
{
52+
"tcId": 1,
53+
"publicServer": "42C4060DF475A484E4236D10F17180AF3B99F6D684DDF870E845881B9071A54E"
54+
},
55+
{
56+
"tcId": 2,
57+
"publicServer": "6F816BF7D5F9A0A1D9455D41A02CFFF189809ADA9DDFB2715C34BBB5F0947603"
58+
},
59+
{
60+
"tcId": 3,
61+
"publicServer": "B11FADD796461DBB7B4744C8D120AB417BA0BCB4D07C19BAC5D35A5A63E1262E"
62+
},
63+
{
64+
"tcId": 4,
65+
"publicServer": "9E50A0F910DF7272063F56E6C1FA0985D67EEDB27A614F1B50F169E553BF6935"
66+
},
67+
{
68+
"tcId": 5,
69+
"publicServer": "E5CC81E9470D117B0A447F857479C1D62A1707CFBBB7C5982EC37E3B0D70445B"
70+
},
71+
{
72+
"tcId": 6,
73+
"publicServer": "1AF998D78E31E5F3E4B239069D59577103BB22BCD74A7F340F0CBEB2E0749656"
74+
},
75+
{
76+
"tcId": 7,
77+
"publicServer": "3E89E54DCBE9864564ED5D9E7AF8C81519296434974415AE911C1E6D979CE86C"
78+
},
79+
{
80+
"tcId": 8,
81+
"publicServer": "48953896FEC80D17357B5172F57267002CAF85F7B8AAD72ABB9A7C974BDC624A"
82+
},
83+
{
84+
"tcId": 9,
85+
"publicServer": "5B1F9736154161C3EDEB6B2CEB0E3EE53ACB8D5E911E93E753C4C000207EC50C"
86+
},
87+
{
88+
"tcId": 10,
89+
"publicServer": "1BC4C7B94927A81D9CA201B5A4C82C379BDF0ADDE6EA0BAECF923252CD5D6673"
90+
},
91+
{
92+
"tcId": 11,
93+
"publicServer": "C7103A622C399D5A6FFFA827E24F49F37398201FA35423BD04C72D025864AC1E"
94+
},
95+
{
96+
"tcId": 12,
97+
"publicServer": "FD9095C9EA7C3A5BD7A59556B81CB537AF1BBE6C6327A95C096090754C5C1120"
98+
},
99+
{
100+
"tcId": 13,
101+
"publicServer": "DDC2FDA36F630995AC295A2F8D743623C193F226C0B91CAA0F03DF3272B2F067"
102+
},
103+
{
104+
"tcId": 14,
105+
"publicServer": "6E0CDA7BB75AD207B30C01B9BF57F8F0143389442BBABCB037C8E1FA6CED1C7E"
106+
},
107+
{
108+
"tcId": 15,
109+
"publicServer": "81DC2EFBCF3054744CAFD5F659355298651DB7B44FA464178DC39318D3CE0E78"
110+
},
111+
{
112+
"tcId": 16,
113+
"publicServer": "0B20799C9FBCBC9E3254CB69422DC043F0315E72EA8A95664EF5C37223D27072"
114+
},
115+
{
116+
"tcId": 17,
117+
"publicServer": "07C3802DDD18F2CB01BB5EADA2F17FBDCC808C5E136D53E739198D5A71DEC61A"
118+
},
119+
{
120+
"tcId": 18,
121+
"publicServer": "50721754D50790BEB2B608EE6538BD33B30E984560186991C1534FAF7B150D12"
122+
},
123+
{
124+
"tcId": 19,
125+
"publicServer": "3D51C7B4479379FC253E2A2B333FE15A3489CEC3C78CB63CACAD2025CD276B07"
126+
},
127+
{
128+
"tcId": 20,
129+
"publicServer": "D5C7A1C50C77D83242F86C8E4E55669238A368898CDE9044AFF0C2EDAEAD2717"
130+
},
131+
{
132+
"tcId": 21,
133+
"publicServer": "75C3281BEB47BB8B757D88924FEA098DE0B14B8AE35904F0850854E2D9E29E0A"
134+
},
135+
{
136+
"tcId": 22,
137+
"publicServer": "5A8650D92D13FE430C0C26E41BBB12B97F7972C0B8D7A58A2942CF15EF505E32"
138+
},
139+
{
140+
"tcId": 23,
141+
"publicServer": "3114BC1DC454A98EBA47097E8F03E7AC1251BBC9EA26A74ABC1B87F6E7756943"
142+
},
143+
{
144+
"tcId": 24,
145+
"publicServer": "B943F23DFECAD4A954D67BD09B66AC34B65C23F844BC4058543A36576A0F361D"
146+
},
147+
{
148+
"tcId": 25,
149+
"publicServer": "18C78BD5BD90B5CAD9F141ACE03C0EDC6D3E76154BF2E7A1700CD9252111BA52"
150+
}
151+
]
152+
}
153+
]
154+
}]

sw/host/tests/crypto/acvp/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rust_binary(
1515
"src/hmac.rs",
1616
"src/main.rs",
1717
"src/rsa.rs",
18+
"src/x25519.rs",
1819
],
1920
deps = [
2021
"//sw/host/cryptotest/ujson_lib:cryptotest_commands",

sw/host/tests/crypto/acvp/src/main.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod cshake;
1919
mod eddsa;
2020
mod hmac;
2121
mod rsa;
22+
mod x25519;
2223

2324
#[derive(Debug, Parser)]
2425
struct Opts {
@@ -87,6 +88,10 @@ enum AcvpVectors {
8788
// vectors match Eddsa first and sigGen vectors fall through to EddsaSigGen.
8889
Eddsa(eddsa::EddsaTestVectorSet),
8990
EddsaSigGen(eddsa::EddsaSignGenTestVectorSet),
91+
// X25519TestVectorSet must precede EddsaKeyGen: EddsaKeygenTestVectorSet test
92+
// cases only require `tc_id`, so they would silently match x25519 test cases
93+
// (which also have `tc_id` plus `publicServer`) if X25519 came later.
94+
X25519(x25519::X25519TestVectorSet),
9095
// EddsaKeygenTestVectorSet has no `preHash` in groups, so sigGen vectors
9196
// (which require preHash) fall through to EddsaKeyGen.
9297
EddsaKeyGen(eddsa::EddsaKeygenTestVectorSet),
@@ -110,6 +115,7 @@ enum AcvpResults {
110115
Eddsa(eddsa::EddsaResultVectorSet),
111116
RsaSigGen(rsa::RsaSignGenResultVectorSet),
112117
Rsa(rsa::RsaResultVectorSet),
118+
X25519(x25519::X25519ResultVectorSet),
113119
}
114120

115121
fn validate_subset(actual: &[AcvpResults], expected_json: &serde_json::Value) -> Result<()> {
@@ -285,6 +291,13 @@ fn run<R: std::io::Read, W: std::io::Write>(
285291
)?));
286292
}
287293
}
294+
AcvpVectors::X25519(vs) => {
295+
acvp_results.push(AcvpResults::X25519(x25519::run_x25519_vector_set(
296+
opts.timeout,
297+
&spi_console_device,
298+
&vs,
299+
)?));
300+
}
288301
}
289302
}
290303
if let Some(w) = output {
@@ -375,7 +388,6 @@ fn main() -> Result<()> {
375388
}
376389
None => None,
377390
};
378-
379391
run(
380392
&opts,
381393
&transport,
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
use anyhow::Result;
6+
use arrayvec::ArrayVec;
7+
use hex::FromHex;
8+
use serde::{Deserialize, Serialize};
9+
use std::time::Duration;
10+
11+
use cryptotest_commands::commands::CryptotestCommand;
12+
use cryptotest_commands::x25519_commands::{
13+
CryptotestX25519KexOutput, CryptotestX25519PublicKey, X25519Subcommand,
14+
};
15+
16+
use opentitanlib::console::spi::SpiConsoleDevice;
17+
use opentitanlib::test_utils::rpc::{ConsoleRecv, ConsoleSend};
18+
19+
// Input structs (from x25519_vectors.hjson)
20+
21+
#[derive(Deserialize, PartialEq, Serialize)]
22+
#[serde(rename_all = "camelCase")]
23+
struct X25519TestCase {
24+
tc_id: usize,
25+
public_server: String,
26+
}
27+
28+
#[derive(Deserialize, PartialEq, Serialize)]
29+
#[serde(rename_all = "camelCase")]
30+
struct X25519TestGroup {
31+
tg_id: usize,
32+
#[allow(dead_code)]
33+
test_type: String,
34+
#[allow(dead_code)]
35+
curve: String,
36+
tests: Vec<X25519TestCase>,
37+
}
38+
39+
#[derive(Deserialize, PartialEq, Serialize)]
40+
#[serde(rename_all = "camelCase")]
41+
pub struct X25519TestVectorSet {
42+
vs_id: usize,
43+
algorithm: String,
44+
mode: String,
45+
revision: String,
46+
#[serde(default)]
47+
is_sample: bool,
48+
test_groups: Vec<X25519TestGroup>,
49+
}
50+
51+
// Output structs (for the JSON result file)
52+
53+
#[derive(Deserialize, PartialEq, Serialize)]
54+
#[serde(rename_all = "camelCase")]
55+
struct X25519ResultCase {
56+
tc_id: usize,
57+
public_iut: String,
58+
z: String,
59+
}
60+
61+
#[derive(Deserialize, PartialEq, Serialize)]
62+
#[serde(rename_all = "camelCase")]
63+
struct X25519ResultGroup {
64+
tg_id: usize,
65+
tests: Vec<X25519ResultCase>,
66+
}
67+
68+
#[derive(Deserialize, PartialEq, Serialize)]
69+
#[serde(rename_all = "camelCase")]
70+
pub struct X25519ResultVectorSet {
71+
vs_id: usize,
72+
algorithm: String,
73+
mode: String,
74+
revision: String,
75+
#[serde(default)]
76+
is_sample: bool,
77+
test_groups: Vec<X25519ResultGroup>,
78+
}
79+
80+
fn run_x25519_case(
81+
timeout: Duration,
82+
spi_console: &SpiConsoleDevice,
83+
tc: &X25519TestCase,
84+
) -> Result<X25519ResultCase> {
85+
let public_server = Vec::<u8>::from_hex(&tc.public_server)?;
86+
87+
CryptotestCommand::X25519.send(spi_console)?;
88+
X25519Subcommand::X25519KEX.send(spi_console)?;
89+
90+
CryptotestX25519PublicKey {
91+
public_key: ArrayVec::try_from(public_server.as_slice())
92+
.map_err(|_| std::io::Error::other("X25519 server public key unexpected length"))?,
93+
public_key_len: public_server.len(),
94+
}
95+
.send(spi_console)?;
96+
97+
let output = CryptotestX25519KexOutput::recv(spi_console, timeout, false, false)?;
98+
99+
let public_iut = &output.public_key[..output.public_key_len];
100+
let z = &output.shared_secret[..output.shared_secret_len];
101+
102+
Ok(X25519ResultCase {
103+
tc_id: tc.tc_id,
104+
public_iut: hex::encode_upper(public_iut),
105+
z: hex::encode_upper(z),
106+
})
107+
}
108+
109+
fn run_x25519_group(
110+
timeout: Duration,
111+
spi_console: &SpiConsoleDevice,
112+
tg: &X25519TestGroup,
113+
) -> Result<X25519ResultGroup> {
114+
log::info!("tg_id: {}", tg.tg_id);
115+
116+
let mut result_cases = Vec::new();
117+
for tc in &tg.tests {
118+
log::info!("tc_id: {}", tc.tc_id);
119+
result_cases.push(run_x25519_case(timeout, spi_console, tc)?);
120+
}
121+
122+
Ok(X25519ResultGroup {
123+
tg_id: tg.tg_id,
124+
tests: result_cases,
125+
})
126+
}
127+
128+
pub fn run_x25519_vector_set(
129+
timeout: Duration,
130+
spi_console: &SpiConsoleDevice,
131+
vs: &X25519TestVectorSet,
132+
) -> Result<X25519ResultVectorSet> {
133+
log::info!("vs_id: {}", vs.vs_id);
134+
135+
let mut result_groups = Vec::with_capacity(vs.test_groups.len());
136+
for tg in &vs.test_groups {
137+
result_groups.push(run_x25519_group(timeout, spi_console, tg)?);
138+
}
139+
140+
Ok(X25519ResultVectorSet {
141+
vs_id: vs.vs_id,
142+
algorithm: vs.algorithm.clone(),
143+
mode: vs.mode.clone(),
144+
revision: vs.revision.clone(),
145+
is_sample: vs.is_sample,
146+
test_groups: result_groups,
147+
})
148+
}

0 commit comments

Comments
 (0)