Skip to content

Commit 8727953

Browse files
committed
[cryptotest] Test RSA encrypt using wycheproof vectors
This commit extends the cryptotest framework to test the RSA encrypt function. To test whether the encrypt was successful, the test takes the ciphertext, decrypts it and compares it to the plaintext that was fed into the encryption. Signed-off-by: Pascal Nasahl <[email protected]>
1 parent acc5868 commit 8727953

File tree

5 files changed

+247
-2
lines changed

5 files changed

+247
-2
lines changed

sw/device/tests/crypto/cryptotest/BUILD

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,25 @@ RSA_TESTVECTOR_TARGETS = [
166166
"sha512_mgf1_64",
167167
"shake256",
168168
]
169+
] + [
170+
"//sw/host/cryptotest/testvectors/data:wycheproof_rsa_oaep_2048_{}.json".format(hash)
171+
for hash in [
172+
"sha256_mgf1sha256",
173+
"sha384_mgf1sha384",
174+
"sha512_mgf1sha512",
175+
]
176+
] + [
177+
"//sw/host/cryptotest/testvectors/data:wycheproof_rsa_oaep_3072_{}.json".format(hash)
178+
for hash in [
179+
"sha256_mgf1sha256",
180+
"sha512_mgf1sha512",
181+
]
182+
] + [
183+
"//sw/host/cryptotest/testvectors/data:wycheproof_rsa_oaep_4096_{}.json".format(hash)
184+
for hash in [
185+
"sha256_mgf1sha256",
186+
"sha512_mgf1sha512",
187+
]
169188
]
170189

171190
RSA_TESTVECTOR_ARGS = " ".join([

sw/device/tests/crypto/cryptotest/firmware/rsa.c

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,140 @@ enum {
5151
kCryptotestRsaShake256 = 7,
5252
};
5353

54+
status_t handle_rsa_encrypt(ujson_t *uj) {
55+
cryptotest_rsa_encrypt_t uj_input;
56+
TRY(ujson_deserialize_cryptotest_rsa_encrypt_t(uj, &uj_input));
57+
58+
if (uj_input.padding != kCryptotestRsaPaddingOaep) {
59+
LOG_ERROR("Unsupported RSA padding: %d", uj_input.padding);
60+
return INVALID_ARGUMENT();
61+
}
62+
63+
if (uj_input.e != kCryptotestRsaSupportedE) {
64+
LOG_ERROR("Unsupported RSA public exponent e: %d", uj_input.e);
65+
return INVALID_ARGUMENT();
66+
}
67+
68+
size_t rsa_num_words;
69+
size_t public_key_bytes;
70+
otcrypto_rsa_size_t rsa_size;
71+
size_t n_bytes = uj_input.security_level / 8;
72+
switch (n_bytes) {
73+
case kOtcryptoRsa2048PublicKeyBytes:
74+
rsa_size = kOtcryptoRsaSize2048;
75+
rsa_num_words = kCryptotestRsa2048NumWords;
76+
public_key_bytes = kOtcryptoRsa2048PublicKeyBytes;
77+
break;
78+
case kOtcryptoRsa3072PublicKeyBytes:
79+
rsa_size = kOtcryptoRsaSize3072;
80+
rsa_num_words = kCryptotestRsa3072NumWords;
81+
public_key_bytes = kOtcryptoRsa3072PublicKeyBytes;
82+
break;
83+
case kOtcryptoRsa4096PublicKeyBytes:
84+
rsa_size = kOtcryptoRsaSize4096;
85+
rsa_num_words = kCryptotestRsa4096NumWords;
86+
public_key_bytes = kOtcryptoRsa4096PublicKeyBytes;
87+
break;
88+
default:
89+
LOG_ERROR("Unsupported RSA security_level: %d", uj_input.security_level);
90+
return INVALID_ARGUMENT();
91+
}
92+
93+
otcrypto_hash_mode_t hash_mode;
94+
switch (uj_input.hashing) {
95+
case kCryptotestRsaSha256:
96+
hash_mode = kOtcryptoHashModeSha256;
97+
break;
98+
case kCryptotestRsaSha384:
99+
hash_mode = kOtcryptoHashModeSha384;
100+
break;
101+
case kCryptotestRsaSha512:
102+
hash_mode = kOtcryptoHashModeSha512;
103+
break;
104+
case kCryptotestRsaSha3_256:
105+
hash_mode = kOtcryptoHashModeSha3_256;
106+
break;
107+
case kCryptotestRsaSha3_384:
108+
hash_mode = kOtcryptoHashModeSha3_384;
109+
break;
110+
case kCryptotestRsaSha3_512:
111+
hash_mode = kOtcryptoHashModeSha3_512;
112+
break;
113+
case kCryptotestRsaShake128:
114+
hash_mode = kOtcryptoHashXofModeShake128;
115+
break;
116+
case kCryptotestRsaShake256:
117+
hash_mode = kOtcryptoHashXofModeShake256;
118+
break;
119+
default:
120+
LOG_ERROR("Unsupported RSA hash mode: %d", uj_input.hashing);
121+
return INVALID_ARGUMENT();
122+
}
123+
124+
// Create the modulus N buffer.
125+
uint32_t n_buf[rsa_num_words];
126+
memset(n_buf, 0, sizeof(n_buf));
127+
memcpy(n_buf, uj_input.n, n_bytes);
128+
129+
otcrypto_const_word32_buf_t modulus = {
130+
.data = n_buf,
131+
.len = rsa_num_words,
132+
};
133+
134+
// Construct the public key.
135+
uint32_t public_key_data[ceil_div(public_key_bytes, sizeof(uint32_t))];
136+
137+
otcrypto_unblinded_key_t public_key = {
138+
.key_mode = kOtcryptoKeyModeRsaEncryptOaep,
139+
.key_length = public_key_bytes,
140+
.key = public_key_data,
141+
};
142+
143+
TRY(otcrypto_rsa_public_key_construct(rsa_size, modulus, &public_key));
144+
145+
// Create input message.
146+
uint8_t msg_buf[rsa_num_words];
147+
memset(msg_buf, 0, sizeof(msg_buf));
148+
memcpy(msg_buf, uj_input.plaintext, uj_input.plaintext_len);
149+
otcrypto_const_byte_buf_t input_message = {
150+
.len = uj_input.plaintext_len,
151+
.data = msg_buf,
152+
};
153+
154+
// Create label.
155+
uint8_t label_buf[uj_input.label_len];
156+
memset(label_buf, 0, sizeof(label_buf));
157+
memcpy(label_buf, uj_input.label, uj_input.label_len);
158+
otcrypto_const_byte_buf_t label = {
159+
.data = label_buf,
160+
.len = uj_input.label_len,
161+
};
162+
163+
// Output buffer.
164+
uint32_t ciphertext_buf[rsa_num_words];
165+
otcrypto_word32_buf_t ciphertext = {
166+
.data = ciphertext_buf,
167+
.len = rsa_num_words,
168+
};
169+
170+
bool status_resp = true;
171+
otcrypto_status_t status = otcrypto_rsa_encrypt(
172+
&public_key, hash_mode, input_message, label, ciphertext);
173+
if (status.value != kOtcryptoStatusValueOk) {
174+
status_resp = false;
175+
}
176+
177+
// Return ciphertext and the status back to host.
178+
cryptotest_rsa_encrypt_resp_t uj_output;
179+
memset(uj_output.ciphertext, 0, RSA_CMD_MAX_MESSAGE_BYTES);
180+
memcpy(uj_output.ciphertext, ciphertext_buf, n_bytes);
181+
uj_output.ciphertext_len = n_bytes;
182+
uj_output.result = status_resp;
183+
184+
RESP_OK(ujson_serialize_cryptotest_rsa_encrypt_resp_t, uj, &uj_output);
185+
return OK_STATUS();
186+
}
187+
54188
status_t handle_rsa_decrypt(ujson_t *uj) {
55189
cryptotest_rsa_decrypt_t uj_input;
56190
TRY(ujson_deserialize_cryptotest_rsa_decrypt_t(uj, &uj_input));
@@ -408,6 +542,8 @@ status_t handle_rsa(ujson_t *uj) {
408542
rsa_subcommand_t cmd;
409543
TRY(ujson_deserialize_rsa_subcommand_t(uj, &cmd));
410544
switch (cmd) {
545+
case kRsaSubcommandRsaEncrypt:
546+
return handle_rsa_encrypt(uj);
411547
case kRsaSubcommandRsaDecrypt:
412548
return handle_rsa_decrypt(uj);
413549
case kRsaSubcommandRsaVerify:

sw/device/tests/crypto/cryptotest/firmware/rsa.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "sw/device/lib/ujson/ujson.h"
1010

1111
status_t handle_rsa_decrypt(ujson_t *uj);
12+
status_t handle_rsa_encrypt(ujson_t *uj);
1213
status_t handle_rsa_verify(ujson_t *uj);
1314
status_t handle_rsa(ujson_t *uj);
1415

sw/device/tests/crypto/cryptotest/json/rsa_commands.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern "C" {
1818
// clang-format off
1919

2020
#define RSA_SUBCOMMAND(_, value) \
21+
value(_, RsaEncrypt) \
2122
value(_, RsaDecrypt) \
2223
value(_, RsaVerify)
2324
UJSON_SERDE_ENUM(RsaSubcommand, rsa_subcommand_t, RSA_SUBCOMMAND);
@@ -47,6 +48,18 @@ UJSON_SERDE_STRUCT(CryptotestRsaVerify, cryptotest_rsa_verify_t, RSA_VERIFY);
4748
field(padding, size_t)
4849
UJSON_SERDE_STRUCT(CryptotestRsaDecrypt, cryptotest_rsa_decrypt_t, RSA_DECRYPT);
4950

51+
#define RSA_ENCRYPT(field, string) \
52+
field(plaintext, uint8_t, RSA_CMD_MAX_MESSAGE_BYTES) \
53+
field(plaintext_len, size_t) \
54+
field(e, uint32_t) \
55+
field(n, uint8_t, RSA_CMD_MAX_N_BYTES) \
56+
field(security_level, size_t) \
57+
field(label, uint8_t, RSA_CMD_MAX_MESSAGE_BYTES) \
58+
field(label_len, size_t) \
59+
field(hashing, size_t) \
60+
field(padding, size_t)
61+
UJSON_SERDE_STRUCT(CryptotestRsaEncrypt, cryptotest_rsa_encrypt_t, RSA_ENCRYPT);
62+
5063
#define RSA_VERIFY_RESP(field, string) \
5164
field(result, bool)
5265
UJSON_SERDE_STRUCT(CryptotestRsaVerifyResp, cryptotest_rsa_verify_resp_t, RSA_VERIFY_RESP);
@@ -57,6 +70,12 @@ UJSON_SERDE_STRUCT(CryptotestRsaVerifyResp, cryptotest_rsa_verify_resp_t, RSA_VE
5770
field(result, bool)
5871
UJSON_SERDE_STRUCT(CryptotestRsaDecryptResp, cryptotest_rsa_decrypt_resp_t, RSA_DECRYPT_RESP);
5972

73+
#define RSA_ENCRYPT_RESP(field, string) \
74+
field(ciphertext, uint8_t, RSA_CMD_MAX_MESSAGE_BYTES) \
75+
field(ciphertext_len, size_t) \
76+
field(result, bool)
77+
UJSON_SERDE_STRUCT(CryptotestRsaEncryptResp, cryptotest_rsa_encrypt_resp_t, RSA_ENCRYPT_RESP);
78+
6079
#undef MODULE_ID
6180

6281
// clang-format on

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

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use serde::Deserialize;
1212

1313
use cryptotest_commands::commands::CryptotestCommand;
1414
use cryptotest_commands::rsa_commands::{
15-
CryptotestRsaDecrypt, CryptotestRsaDecryptResp, CryptotestRsaVerify, CryptotestRsaVerifyResp,
16-
RsaSubcommand,
15+
CryptotestRsaDecrypt, CryptotestRsaDecryptResp, CryptotestRsaEncrypt, CryptotestRsaEncryptResp,
16+
CryptotestRsaVerify, CryptotestRsaVerifyResp, RsaSubcommand,
1717
};
1818

1919
use opentitanlib::app::TransportWrapper;
@@ -152,6 +152,76 @@ fn run_rsa_testcase(
152152
);
153153
}
154154
}
155+
"encrypt" => {
156+
// Send RsaEncrypt command.
157+
RsaSubcommand::RsaEncrypt.send(spi_console)?;
158+
159+
// Convert the inputs into the expected format for the CL.
160+
let ptx: Vec<_> = test_case.message.iter().copied().rev().collect();
161+
162+
// Assemble the input.
163+
CryptotestRsaEncrypt {
164+
plaintext: ArrayVec::try_from(ptx.as_slice()).unwrap(),
165+
plaintext_len: test_case.message.len(),
166+
e: test_case.e,
167+
n: ArrayVec::try_from(n.as_slice()).unwrap(),
168+
security_level: test_case.security_level,
169+
label: ArrayVec::try_from(test_case.label.as_slice()).unwrap(),
170+
label_len: test_case.label.len(),
171+
hashing,
172+
padding,
173+
}
174+
.send(spi_console)?;
175+
176+
// Get and evaluate the response.
177+
let rsa_encrypt_resp =
178+
CryptotestRsaEncryptResp::recv(spi_console, opts.timeout, false)?;
179+
// Check if the encryption was successful.
180+
assert_eq!(rsa_encrypt_resp.result, test_case.result);
181+
182+
// Use the received ciphertext, decrypt it, and compare it to the
183+
// plaintext in the test vector.
184+
if test_case.result {
185+
// Decrypt it again and check if the plaintext matches.
186+
// Send RsaDecrypt command.
187+
CryptotestCommand::Rsa.send(spi_console)?;
188+
RsaSubcommand::RsaDecrypt.send(spi_console)?;
189+
190+
// Convert the inputs into the expected format for the CL.
191+
let d: Vec<_> = test_case.d.iter().copied().rev().collect();
192+
let ctx: Vec<_> =
193+
Vec::from(&rsa_encrypt_resp.ciphertext[..rsa_encrypt_resp.ciphertext_len]);
194+
195+
// Assemble the input.
196+
CryptotestRsaDecrypt {
197+
ciphertext: ArrayVec::try_from(ctx.as_slice()).unwrap(),
198+
ciphertext_len: rsa_encrypt_resp.ciphertext_len,
199+
e: test_case.e,
200+
d: ArrayVec::try_from(d.as_slice()).unwrap(),
201+
n: ArrayVec::try_from(n.as_slice()).unwrap(),
202+
security_level: test_case.security_level,
203+
label: ArrayVec::try_from(test_case.label.as_slice()).unwrap(),
204+
label_len: test_case.label.len(),
205+
hashing,
206+
padding,
207+
}
208+
.send(spi_console)?;
209+
210+
// Get and evaluate the response.
211+
let rsa_decrypt_resp =
212+
CryptotestRsaDecryptResp::recv(spi_console, opts.timeout, false)?;
213+
// Check if the decryption was successful.
214+
assert_eq!(rsa_decrypt_resp.result, test_case.result);
215+
216+
if test_case.result {
217+
// Only check plaintext if the response is valid.
218+
assert_eq!(
219+
rsa_decrypt_resp.plaintext[0..test_case.message.len()],
220+
test_case.message[0..test_case.message.len()]
221+
);
222+
}
223+
}
224+
}
155225
_ => panic!("Invalid operation"),
156226
};
157227

0 commit comments

Comments
 (0)