Skip to content

Commit a40fd11

Browse files
committed
feat: add PUBLIC_KEY_SIZE constant and update key handling in KEM and ticket modules
1 parent e524f2d commit a40fd11

6 files changed

Lines changed: 125 additions & 36 deletions

File tree

crates/crypt/src/kem.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use libcrux_ml_kem::mlkem768::generate_key_pair as ml_kem_generate_key_pair;
1111

1212
// Note, changes to kem size (1024, 768 or 512) will need to update also SECRET_KEY_SIZE and CIPHERTEXT_SIZE
1313
pub const SECRET_KEY_SIZE: usize = 2400;
14+
pub const PUBLIC_KEY_SIZE: usize = 1184;
1415
pub const CIPHERTEXT_SIZE: usize = 1088;
1516

1617
/// Generate a new KEM keypair (private key and public key)

crates/crypt/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod ticket;
55
pub use ticket::{Ticket, TunnelMaterial};
66

77
mod kem;
8-
pub use kem::{CIPHERTEXT_SIZE, SECRET_KEY_SIZE, generate_key_pair};
8+
pub use kem::{CIPHERTEXT_SIZE, SECRET_KEY_SIZE, PUBLIC_KEY_SIZE, generate_key_pair};
99

1010
#[cfg(test)]
1111
mod tests;

crates/crypt/src/tests.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,22 @@ const TEST_TICKET_JSON: &str = r#"{
6565
// }
6666
// }
6767

68-
fn get_private_key_bytes() -> Result<Vec<u8>> {
68+
fn get_private_key_bytes() -> Result<[u8; SECRET_KEY_SIZE]> {
6969
let kem_private_key_bytes = general_purpose::STANDARD
7070
.decode(PRIVATE_KEY_768_TESTING)
7171
.map_err(|e| anyhow::format_err!("Failed to decode base64 KEM private key: {}", e))?;
7272
let kem_private_key_bytes: [u8; SECRET_KEY_SIZE] = kem_private_key_bytes
7373
.try_into()
7474
.map_err(|_| anyhow::format_err!("Invalid KEM private key size"))?;
75-
Ok(kem_private_key_bytes.to_vec())
75+
Ok(kem_private_key_bytes)
7676
}
7777

7878
#[test]
7979
fn test_recover_invalid_data_from_json() {
8080
let ticket = Ticket::new("AES-256-GCM", "", "");
8181

8282
let result =
83-
ticket.recover_data_from_json(TICKET_ID_TESTING.as_bytes(), get_private_key_bytes().unwrap());
83+
ticket.recover_data_from_json(TICKET_ID_TESTING.as_bytes(), &get_private_key_bytes().unwrap());
8484
assert!(result.is_err());
8585
}
8686

@@ -89,7 +89,7 @@ fn test_recover_valid_data_from_json() {
8989
let ticket: Ticket = serde_json::from_str(TEST_TICKET_JSON).unwrap();
9090

9191
let result =
92-
ticket.recover_data_from_json(TICKET_ID_TESTING.as_bytes(), get_private_key_bytes().unwrap());
92+
ticket.recover_data_from_json(TICKET_ID_TESTING.as_bytes(), &get_private_key_bytes().unwrap());
9393
assert!(
9494
result.is_ok(),
9595
"Failed to recover data from JSON ticket: {:?}",

crates/crypt/src/ticket.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,8 @@ impl Ticket {
7272
pub fn recover_data_from_json(
7373
&self,
7474
ticket_id: &[u8],
75-
kem_private_key: Vec<u8>,
75+
kem_private_key: &[u8; SECRET_KEY_SIZE],
7676
) -> Result<serde_json::Value> {
77-
let kem_private_key: [u8; SECRET_KEY_SIZE] = kem_private_key
78-
.try_into()
79-
.map_err(|_| anyhow::format_err!("Invalid KEM private key size"))?;
80-
8177
let kem_private_key = PrivateKey::from(kem_private_key);
8278

8379
// Extract shared_secret from KEM ciphertext

crates/shared/src/broker/api/mod.rs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131

3232
use anyhow::Result;
3333
use async_trait::async_trait;
34-
use reqwest::{Client, ClientBuilder};
3534
use base64::{Engine as _, engine::general_purpose};
35+
use reqwest::{Client, ClientBuilder};
3636

3737
use crate::{consts, log};
38-
use crypt::{Ticket, generate_key_pair};
38+
use crypt::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, Ticket, generate_key_pair};
3939

4040
pub mod types;
4141

@@ -54,6 +54,8 @@ pub struct UdsBrokerApi {
5454
client: Client,
5555
broker_url: String,
5656
hostname: String,
57+
public_key: [u8; PUBLIC_KEY_SIZE],
58+
private_key: [u8; SECRET_KEY_SIZE],
5759
}
5860

5961
impl UdsBrokerApi {
@@ -74,13 +76,31 @@ impl UdsBrokerApi {
7476
builder = builder.no_proxy();
7577
}
7678

77-
// panic if client cannot be built, as this is a programming error (invalid URL, etc)
79+
80+
// Note: unwraps are intentinonal here, if we cannot build the client, we want to
81+
// abort early.
82+
7883
let client = builder.build().unwrap();
7984

85+
// Generate ephemeral KEM keypair
86+
let (private_key, public_key) = generate_key_pair().unwrap();
87+
8088
Self {
8189
client,
8290
broker_url: broker_url.to_string().trim_end_matches('/').to_string(),
8391
hostname: hostname::get().unwrap().to_string_lossy().to_string(),
92+
public_key: public_key.try_into().unwrap(),
93+
private_key: private_key.try_into().unwrap(),
94+
}
95+
}
96+
97+
// Only for tests
98+
#[cfg(test)]
99+
pub fn with_keys(self, private_key: [u8; SECRET_KEY_SIZE], public_key: [u8; PUBLIC_KEY_SIZE]) -> Self {
100+
Self {
101+
public_key,
102+
private_key,
103+
..self
84104
}
85105
}
86106

@@ -126,43 +146,35 @@ impl BrokerApi for UdsBrokerApi {
126146
scrambler
127147
);
128148

129-
// Generate ephemeral KEM keypair
130-
let (private_key, public_key) = generate_key_pair()?;
131-
132149
// Prepare request body
133150
let req: types::TicketReqBody = types::TicketReqBody {
134151
scrambler,
135-
kem_kyber_key: &general_purpose::STANDARD.encode(&public_key),
152+
kem_kyber_key: &general_purpose::STANDARD.encode(self.public_key),
136153
hostname: &self.hostname,
137154
version: consts::UDS_CLIENT_VERSION,
138155
};
139156

140157
let response = self
141158
.client
142-
.post(format!(
143-
"{}/{}/ticket",
144-
self.broker_url,
145-
ticket,
146-
))
159+
.post(format!("{}/{}/ticket", self.broker_url, ticket,))
147160
.json(&req)
148161
.headers(self.headers())
149162
.send()
150163
.await?;
151164

152165
// Extract real script info from Ticket
153166
response
154-
.json::<types::BrokerResponse<Ticket>>()
155-
.await?
156-
.into_result()?
157-
.recover_data_from_json(ticket.as_bytes(), private_key)
158-
.map(|json_value| {
159-
serde_json::from_value::<types::Script>(json_value)
160-
.map_err(|e| types::Error {
161-
message: format!("Failed to parse script from ticket data: {}", e),
162-
is_retryable: false,
163-
percent: 0,
164-
})
165-
})?
167+
.json::<types::BrokerResponse<Ticket>>()
168+
.await?
169+
.into_result()?
170+
.recover_data_from_json(ticket.as_bytes(), &self.private_key)
171+
.map(|json_value| {
172+
serde_json::from_value::<types::Script>(json_value).map_err(|e| types::Error {
173+
message: format!("Failed to parse script from ticket data: {}", e),
174+
is_retryable: false,
175+
percent: 0,
176+
})
177+
})?
166178

167179
// response
168180
// .json::<types::BrokerResponse<types::Script>>()

crates/shared/src/broker/api/tests.rs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,68 @@ use crate::log;
3535

3636
use mockito::Server;
3737

38+
const PRIVATE_KEY_768_TESTING: &str = "TzpPr8sQk1BBjmEFpTqCqdhTNfGdTpK37GBFaQWnigW8AZqMzrlSxRa+grYDdjJ1JiaiuSkpptCtIKsf\
39+
6QiD6HRJrAPNCJyxbmihz3KS0IOmjzUx4BYh/Ap/nYbE/0qWZFG0KdGKtSKWnOoQFCph0vOLQKnN8HGq\
40+
ZMty+qxBWDZ7qJAQxaU2SpJSzPEeakmP6jxvQTjIMXTGN/iD8ViqntbIn7uyWoZmhpwMu0ioCvYZiahl\
41+
UlgGOkdnlEojxIphbBaAoaoZ6im1u+g8SeTKnEaQGkWZLJSak9RSuLin5AgFS6KomFY1ddkYpSMAN+dC\
42+
3fWh3FHLicevB2hnxLsiaDxUvaduApavjCc3JMBQtkgvx1d5YaERlFaKIjAbAIoCqwyX7TOih5YtmdVk\
43+
BGRRVscr9TYz4QKPAJRjskoF0kqeskGzU/kSzfynnDvK+mmCP8Cd3fCVraMarQZHJAxjZhsQeheu0CgN\
44+
k+iDq3MyITxOSyR/POdasAVTmfZ6O/GdN0UcDtuDB6hNQrQ2CNSZAcGkQfqHaTt+vbJfpkKwf1UCGAoq\
45+
mEKk77Z5eueqMMZWffQhCARScRCWGOgscjQ+z1ItTpGKD2Rbqvk6hvOZYIxauiZfXtC02gchxaKg39x+\
46+
nzzHLcxzhWYK9RERiUsS8IuDvqaCcwJEOpQfSOGgYcZk8uRRf5ioGGlcUErID/SL9lZLqUxIDXMq4ygr\
47+
yXsvYWzMhPhTMUy2irsAtNiBdFNjKvR1HAaVYEfAWXJMaJzBagF0NRmza8pIxWF7paQZvteLdVDOjExe\
48+
RogBc4uUjFTA6vpsP2IeOBpPcqR/wBZcjosAcyQA75Chk+KNlmAiy4rAYFi5i+gzK2mSJPw7wOy5eppz\
49+
ZCJ2Q9eOAzlLfJU+5yAStGRngxBw8OXIO/cSU9JtHNUXPtaoDfDGkJkEfwifdbJDOMSyi1yxq+ZJ4TGm\
50+
faRItvUeuRiJSXV7x6kMGfqpzfN8P6cCRlqM8cqkRWG3uCGcxFWQh7UYVgNo6xNQPQafFpkm/KoW2Esg\
51+
ZaUpJBJeF0kMA7hGd7G/zdHDDFsRtcUV/GQcM+ERZwwswnYm1IlYRaRK3/gQxKNANsS9+GIDsvUU1Xd+\
52+
D2NIftaW4HifZWN08VWHdICU85TIlgAfStFSsPZQV9VmZnaEulsaFYCkcUjGFhkdezIcVJKymyWV5Gyi\
53+
KCJqAGSez8SMmtyBb+FonMKrezZXY0NMJUmvw4N8ybw+bHLM2tGgPtp3ZKnLN7Ek2nSijDYdtXuNZsAu\
54+
Q7xzF6ZC9giNBBkFf1MtDsyUZahpPVXJLnigewV7SCZTj9vDkzy05Tkf1Wtt46DKnqLK78ycRQkP6eeM\
55+
L7UJjuVejXmrAyOO5Ctmp7Wt/up5yZecxIcRpxCHV4d5kzZ0PsaLQTE6sRqcQLghnQVJhIxYECqEODoQ\
56+
sRm17nJAu/QMj3GRnNVzUTM+bxczopRntppe+hqenNAcx1Snv3lGblw0d/ZfZWExkpPMP0IhJwu2ZEzA\
57+
LCgc95w/uVOTzSd9d7qwr9yBu2K0fpAAsYdTyHRCL+iLaxBf5/F019DFOFbC+HY8PgVOH+y2uicK/5td\
58+
3lpfzjm302eDDxJ98+O2Cpt9R7yQEWCOVWvH26Z2k2BgXSmsovxqvtu5xVKvLOkpKEZyHpGgiumpSxS+\
59+
0TE5Dgh2eQBJhmSkrysKbFWB0QUCQXAaH7BbQMhftKfIJ0W7ckeNilyZDGh2dVlN7Ohl1Bq0L0wvgvNm\
60+
prFne3uCYmddE2F1Xrh/rwbDZvzKf5WRT+DJStdIOVxSV8c4PHkLnls1/XllgaM/9uNEgMtyWKOBHDmY\
61+
FqCWnsMMlgoB8IGDIJKuDCSJuVNQVBuLGiDDrVye16dJpOS9B0uBt3sntvgy/PKSooVuamq3A8k/LzGc\
62+
Ffw9fIOLccKfFhhIcbllN9y1C2kYBHo6Lxxb5yNPmUsIdFAsRkg5cWaHh/gA1uEAXDIzttF2WYJEZfej\
63+
6Hd+dWAJfowOAEnAe0KPz2KZBxocJRhndjFp6gxpapesa1mmd2dhu7aqZptAbmCt5tUyXNsjguGCmbqQ\
64+
7cMc5zKLQbu/U0K8i2SD1pxrMgxa4UkKsTVWeMUKKzufCllPdYiiLmAgIZUO3xxK0vsQ45Cq+9xhlWWG\
65+
61olnlqyx9EoffQh3IAR1HO0twEXqCt+BNU5x2ue3cpgZhwWBLfDBpyRWpeW3eGkbHtLt9OYolQCOMRO\
66+
kLKMEyM1k/pfrtYOl6NDYbEKwJY3NdkKaFG5ROs5dbIcO1IZ8kyzMfhyTRYkNqLAW+ISRTl0+MdPaIU6\
67+
MtVHA2h7fpsVw6NxoaASIdEhNIaOy0NRIFZQILsvXSTM3pcHmYkk6QhgIcN/s/anc7KWNsS/TQsvuzXE\
68+
wFS4GDsAv+rPG3jAvrh/MqMAgMAh2GWI9LBaYgFngrA3U2xNGho69jFG2QtcVgc93kyns1cQuGWSL2li\
69+
xdDGcwUjTetjegNJUehiz2ZJ7qadQlNPvJOT1dpOcPWKH/moz6gNOrw2LrRHZAOLdiS6arcmCWm/Xhq8\
70+
gznKn7k178w0lRatnGUspBOZDXMNedYjPvKLmGV01ZhUUcET6wWpsbjLLhdEpfMMICW4HZI9GamENvEE\
71+
jZkHzjEXJGWSMFnIvcxLUaWJW/MOqMmjjaabctRzo7dCJ8dKAN2l03UMkkyjF2s1DKlVSkrK+6Mwnnt7\
72+
smF1k3BWU0ctt5BGU8xjP2Vm4npBPTEhpwUJ6LsfZ4pLANddBDHELDAerLuPcWuVc/hnbVxAIUpYK3AS\
73+
eWKi1gdfCymQWaI15OTKSwtXGNUh7bObbmkzjRofhRgTLCmnXhgL9pmX1SUcfck1drunDjjAPPsl3oO2\
74+
99e17AV4CMMugYKXsJZwl6BekOYG1ygz1DgUB0mDPaFVDucPbokk8DMtLOrKu+Bu9rOa7+MdxwYJ+mMj\
75+
Leae9+MURrdHwtYhvaSdDPA8O1h8sGhck2ewpLR3dGCHeyeJsEws1IiCnRa2cqxA+LxenDJV1WeGjWog\
76+
d+MiNioBJ+kzpLpeAFohLlJG5zmHijRc/O4OYbzvH/NisQARHwX3ScApN7qAjG49j2fwJgcrKCNylIo4\
77+
m4CYr3v+mJpFf/v5QyZ8UujroFWV67dujs9/Cre9D31ylzSP2c8CCOg2X9M6bEfY1mzMsGaLau3oXgR2";
78+
79+
const PUBLIC_KEY_768_TESTING: &str = "d7qwr9yBu2K0fpAAsYdTyHRCL+iLaxBf5/F019DFOFbC+HY8PgVOH+y2uicK/5td3lpfzjm302eDDxJ9\
80+
8+O2Cpt9R7yQEWCOVWvH26Z2k2BgXSmsovxqvtu5xVKvLOkpKEZyHpGgiumpSxS+0TE5Dgh2eQBJhmSk\
81+
rysKbFWB0QUCQXAaH7BbQMhftKfIJ0W7ckeNilyZDGh2dVlN7Ohl1Bq0L0wvgvNmprFne3uCYmddE2F1\
82+
Xrh/rwbDZvzKf5WRT+DJStdIOVxSV8c4PHkLnls1/XllgaM/9uNEgMtyWKOBHDmYFqCWnsMMlgoB8IGD\
83+
IJKuDCSJuVNQVBuLGiDDrVye16dJpOS9B0uBt3sntvgy/PKSooVuamq3A8k/LzGcFfw9fIOLccKfFhhI\
84+
cbllN9y1C2kYBHo6Lxxb5yNPmUsIdFAsRkg5cWaHh/gA1uEAXDIzttF2WYJEZfej6Hd+dWAJfowOAEnA\
85+
e0KPz2KZBxocJRhndjFp6gxpapesa1mmd2dhu7aqZptAbmCt5tUyXNsjguGCmbqQ7cMc5zKLQbu/U0K8\
86+
i2SD1pxrMgxa4UkKsTVWeMUKKzufCllPdYiiLmAgIZUO3xxK0vsQ45Cq+9xhlWWG61olnlqyx9EoffQh\
87+
3IAR1HO0twEXqCt+BNU5x2ue3cpgZhwWBLfDBpyRWpeW3eGkbHtLt9OYolQCOMROkLKMEyM1k/pfrtYO\
88+
l6NDYbEKwJY3NdkKaFG5ROs5dbIcO1IZ8kyzMfhyTRYkNqLAW+ISRTl0+MdPaIU6MtVHA2h7fpsVw6Nx\
89+
oaASIdEhNIaOy0NRIFZQILsvXSTM3pcHmYkk6QhgIcN/s/anc7KWNsS/TQsvuzXEwFS4GDsAv+rPG3jA\
90+
vrh/MqMAgMAh2GWI9LBaYgFngrA3U2xNGho69jFG2QtcVgc93kyns1cQuGWSL2lixdDGcwUjTetjegNJ\
91+
Uehiz2ZJ7qadQlNPvJOT1dpOcPWKH/moz6gNOrw2LrRHZAOLdiS6arcmCWm/Xhq8gznKn7k178w0lRat\
92+
nGUspBOZDXMNedYjPvKLmGV01ZhUUcET6wWpsbjLLhdEpfMMICW4HZI9GamENvEEjZkHzjEXJGWSMFnI\
93+
vcxLUaWJW/MOqMmjjaabctRzo7dCJ8dKAN2l03UMkkyjF2s1DKlVSkrK+6Mwnnt7smF1k3BWU0ctt5BG\
94+
U8xjP2Vm4npBPTEhpwUJ6LsfZ4pLANddBDHELDAerLuPcWuVc/hnbVxAIUpYK3ASeWKi1gdfCymQWaI1\
95+
5OTKSwtXGNUh7bObbmkzjRofhRgTLCmnXhgL9pmX1SUcfck1drunDjjAPPsl3oO299e17AV4CMMugYKX\
96+
sJZwl6BekOYG1ygz1DgUB0mDPaFVDucPbokk8DMtLOrKu+Bu9rOa7+MdxwYJ+mMjLeae9+MURrdHwtYh\
97+
vaSdDPA8O1h8sGhck2ewpLR3dGCHeyeJsEws1IiCnRa2cqxA+LxenDJV1WeGjWogd+MiNioBJ+kzpLpe\
98+
AFohLlJG5zmHijRc/O4OYbzvH/NisQARHwX3ScApN7qAjG49j2fwJgcrKCM=";
99+
38100
const TICKET_RESPONSE_JSON: &str = r#"{
39101
"result": {
40102
"algorithm": "AES-256-GCM",
@@ -44,6 +106,22 @@ const TICKET_RESPONSE_JSON: &str = r#"{
44106
}"#;
45107
const TICKET_ID: &str = "c6s9FAa5fhb854BVMckqUBJ4hOXg2iE5i1FYPCuktks4eNZD";
46108

109+
fn get_keypair() -> Result<([u8; SECRET_KEY_SIZE], [u8; PUBLIC_KEY_SIZE])> {
110+
let kem_private_key_bytes = general_purpose::STANDARD
111+
.decode(PRIVATE_KEY_768_TESTING)
112+
.map_err(|e| anyhow::format_err!("Failed to decode base64 KEM private key: {}", e))?;
113+
let kem_private_key_bytes: [u8; SECRET_KEY_SIZE] = kem_private_key_bytes
114+
.try_into()
115+
.map_err(|_| anyhow::format_err!("Invalid KEM private key size"))?;
116+
let kem_public_key_bytes = general_purpose::STANDARD
117+
.decode(PUBLIC_KEY_768_TESTING)
118+
.map_err(|e| anyhow::format_err!("Failed to decode base64 KEM public key: {}", e))?;
119+
let kem_public_key_bytes: [u8; PUBLIC_KEY_SIZE] = kem_public_key_bytes
120+
.try_into()
121+
.map_err(|_| anyhow::format_err!("Invalid KEM public key size"))?;
122+
Ok((kem_private_key_bytes, kem_public_key_bytes))
123+
}
124+
47125
// Helper to create a ServerRestApi pointing to mockito server
48126
// Helper to create a mockito server and a ServerRestApi pointing to it
49127
async fn setup_server_and_api() -> (mockito::ServerGuard, UdsBrokerApi) {
@@ -89,6 +167,8 @@ async fn test_get_version() {
89167
async fn test_get_script() {
90168
log::setup_logging("debug", log::LogType::Tests);
91169
let (mut server, api) = setup_server_and_api().await;
170+
let (privk, pubk) = get_keypair().unwrap();
171+
let api = api.with_keys(privk, pubk);
92172
let _m = server
93173
.mock(
94174
"POST",
@@ -113,14 +193,14 @@ async fn test_get_script_fails() {
113193
let _m = server
114194
.mock(
115195
"POST",
116-
mockito::Matcher::Regex(r"^/ticket/scrabler\?hostname=.*&version=.*$".to_string()),
196+
mockito::Matcher::Regex(format!(r"^/{}/ticket", TICKET_ID)),
117197
)
118198
.match_header("content-type", "application/json")
119199
.with_body(result)
120200
.with_status(200)
121201
.create_async()
122202
.await;
123-
let response = api.get_script("ticket", "scrabler").await;
203+
let response = api.get_script(TICKET_ID, "scrabler").await;
124204
assert!(
125205
response.is_err(),
126206
"Get script succeeded unexpectedly: {:?}",

0 commit comments

Comments
 (0)