1use blueprint_std::rand::{CryptoRng, Rng};
40
41pub mod api_keys;
43pub mod api_tokens;
45pub mod auth_token;
47pub mod certificate_authority;
49pub mod db;
51pub mod models;
53pub mod oauth;
55pub mod paseto_tokens;
57pub mod proxy;
59pub mod request_auth;
61pub mod request_extensions;
63pub mod tls_assets;
65pub mod tls_client;
67pub mod tls_envelope;
69pub mod tls_listener;
71pub mod types;
73pub mod validation;
75
76#[cfg(test)]
77mod test_client;
78
79#[cfg(test)]
80mod tests;
81
82#[derive(Debug, thiserror::Error)]
83pub enum Error {
84 #[error("k256 error: {0}")]
86 K256(k256::ecdsa::Error),
87 #[error("Schnorrkel error: {0}")]
89 Schnorrkel(schnorrkel::SignatureError),
90
91 #[error(transparent)]
92 RocksDB(#[from] rocksdb::Error),
93
94 #[error("Invalid database compaction style: {0}")]
95 InvalidDBCompactionStyle(String),
96
97 #[error("Invalid database compression type: {0}")]
98 InvalidDBCompressionType(String),
99
100 #[error("unknown database column family: {0}")]
101 UnknownColumnFamily(&'static str),
102
103 #[error(transparent)]
104 ProtobufDecode(#[from] prost::DecodeError),
105
106 #[error("Unknown key type")]
107 UnknownKeyType,
108
109 #[error(transparent)]
110 Uri(#[from] axum::http::uri::InvalidUri),
111
112 #[error("IO error: {0}")]
113 Io(#[from] std::io::Error),
114
115 #[error("TLS envelope error: {0}")]
116 TlsEnvelope(#[from] crate::tls_envelope::TlsEnvelopeError),
117
118 #[error("Certificate generation error: {0}")]
119 Certificate(#[from] rcgen::Error),
120
121 #[error("TLS error: {0}")]
122 Tls(String),
123
124 #[error("Service not found: {0}")]
125 ServiceNotFound(crate::types::ServiceId),
126}
127
128pub fn generate_challenge<R: Rng + CryptoRng>(rng: &mut R) -> [u8; 32] {
132 let mut challenge = [0u8; 32];
133 rng.fill(&mut challenge);
134 challenge
135}
136
137pub fn verify_challenge(
139 challenge: &[u8; 32],
140 signature: &[u8],
141 pub_key: &[u8],
142 key_type: types::KeyType,
143) -> Result<bool, Error> {
144 match key_type {
145 types::KeyType::Unknown => Err(Error::UnknownKeyType),
146 types::KeyType::Ecdsa => verify_challenge_ecdsa(challenge, signature, pub_key),
147 types::KeyType::Sr25519 => verify_challenge_sr25519(challenge, signature, pub_key),
148 }
149}
150
151fn verify_challenge_ecdsa(
153 challenge: &[u8; 32],
154 signature: &[u8],
155 pub_key: &[u8],
156) -> Result<bool, Error> {
157 use k256::ecdsa::signature::hazmat::PrehashVerifier;
158 let pub_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(pub_key).map_err(Error::K256)?;
159 let signature = k256::ecdsa::Signature::try_from(signature).map_err(Error::K256)?;
160 Ok(pub_key.verify_prehash(challenge, &signature).is_ok())
161}
162
163fn verify_challenge_sr25519(
167 challenge: &[u8; 32],
168 signature: &[u8],
169 pub_key: &[u8],
170) -> Result<bool, Error> {
171 const CTX: &[u8] = b"substrate";
173 let pub_key = schnorrkel::PublicKey::from_bytes(pub_key).map_err(Error::Schnorrkel)?;
174 let signature = schnorrkel::Signature::from_bytes(signature).map_err(Error::Schnorrkel)?;
175 Ok(pub_key.verify_simple(CTX, challenge, &signature).is_ok())
176}
177
178#[cfg(test)]
179mod lib_tests {
180 use super::*;
181
182 use crate::types::{KeyType, VerifyChallengeRequest};
183 use k256::ecdsa::SigningKey;
184
185 #[test]
186 fn test_generate_challenge() {
187 let mut rng = blueprint_std::BlueprintRng::new();
189 let challenge1 = generate_challenge(&mut rng);
190
191 let challenge2 = generate_challenge(&mut rng);
193
194 assert_ne!(challenge1, challenge2);
196
197 assert_ne!(challenge1, [0u8; 32]);
199 }
200
201 #[test]
202 fn test_verify_challenge_ecdsa_valid() {
203 let mut rng = blueprint_std::BlueprintRng::new();
204
205 let challenge = generate_challenge(&mut rng);
207
208 let signing_key = SigningKey::random(&mut rng);
210 let verification_key = signing_key.verifying_key();
211 let public_key = verification_key.to_sec1_bytes();
212
213 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
215
216 let result =
218 verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
219 assert!(result.is_ok());
220 assert!(result.unwrap());
221 }
222
223 #[test]
224 fn test_verify_challenge_ecdsa_invalid_signature() {
225 let mut rng = blueprint_std::BlueprintRng::new();
226
227 let challenge = generate_challenge(&mut rng);
229 let different_challenge = generate_challenge(&mut rng);
230
231 let signing_key = SigningKey::random(&mut rng);
233 let verification_key = signing_key.verifying_key();
234 let public_key = verification_key.to_sec1_bytes();
235
236 let signature = &signing_key
238 .sign_prehash_recoverable(&different_challenge)
239 .unwrap()
240 .0;
241 let result =
243 verify_challenge_ecdsa(&challenge, signature.to_bytes().as_slice(), &public_key);
244 assert!(result.is_ok());
245 assert!(!result.unwrap());
246 }
247
248 #[test]
249 fn test_verify_challenge_ecdsa_invalid_key() {
250 let mut rng = blueprint_std::BlueprintRng::new();
251
252 let challenge = generate_challenge(&mut rng);
254
255 let signing_key = SigningKey::random(&mut rng);
257
258 let different_signing_key = SigningKey::random(&mut rng);
260 let different_verification_key = different_signing_key.verifying_key();
261 let different_public_key = different_verification_key.to_sec1_bytes();
262
263 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
265
266 let result = verify_challenge_ecdsa(
268 &challenge,
269 signature.to_bytes().as_slice(),
270 &different_public_key,
271 );
272 assert!(result.is_ok());
273 assert!(!result.unwrap());
274 }
275
276 #[test]
277 fn test_verify_challenge_unknown_key_type() {
278 let mut rng = blueprint_std::BlueprintRng::new();
279 let challenge = generate_challenge(&mut rng);
280 let result = verify_challenge(&challenge, &[0u8; 64], &[0u8; 33], KeyType::Unknown);
281 assert!(matches!(result, Err(Error::UnknownKeyType)));
282 }
283
284 #[test]
285 fn test_verify_challenge_integration() {
286 let mut rng = blueprint_std::BlueprintRng::new();
287
288 let challenge = generate_challenge(&mut rng);
290
291 let signing_key = SigningKey::random(&mut rng);
293 let verification_key = signing_key.verifying_key();
294 let public_key = verification_key.to_sec1_bytes();
295
296 let signature = &signing_key.sign_prehash_recoverable(&challenge).unwrap().0;
298
299 let result = verify_challenge(
301 &challenge,
302 signature.to_bytes().as_slice(),
303 &public_key,
304 KeyType::Ecdsa,
305 );
306 assert!(result.is_ok());
307 assert!(result.unwrap());
308 }
309
310 #[test]
311 fn test_verify_challenge_sr25519_error_handling() {
312 let mut rng = blueprint_std::BlueprintRng::new();
313 let challenge = generate_challenge(&mut rng);
314
315 let invalid_signature = [0u8; 64];
317 let invalid_pub_key = [0u8; 32];
318
319 let result = verify_challenge_sr25519(&challenge, &invalid_signature, &invalid_pub_key);
321 assert!(result.is_err());
322
323 match result {
325 Err(Error::Schnorrkel(_)) => {}
326 _ => panic!("Expected Schnorrkel error"),
327 }
328 }
329
330 #[test]
331 fn js_compat_ecdsa() {
332 let data = serde_json::json!({
335 "pub_key": "020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1",
336 "key_type": "Ecdsa",
337 "challenge": "0000000000000000000000000000000000000000000000000000000000000000",
338 "signature": "26138be19cfc76e800bdcbba5e3bbc5bd79168cd06ea6afd5be6860d23d5e0340c728508ca0b47b49627b5560fbca6cdd92cbf6ac402d0941bba7e42b9d7a20c",
339 "expires_at": 0
340 });
341
342 let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
343 let result = verify_challenge(
344 &req.challenge,
345 &req.signature,
346 &req.challenge_request.pub_key,
347 req.challenge_request.key_type,
348 );
349 assert!(
350 result.is_ok(),
351 "Failed to verify ECDSA challenge: {}",
352 result.err().unwrap()
353 );
354 assert!(result.is_ok(), "ECDSA verification failed");
355 }
356
357 #[test]
358 fn js_compat_sr25519() {
359 let data = serde_json::json!({
362 "pub_key": "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
363 "key_type": "Sr25519",
364 "challenge": "0000000000000000000000000000000000000000000000000000000000000000",
365 "signature": "f05fa2a2074d5295a34aae0d5383792a6cc34304c9cb4f6a0c577df4b374fe7bab051bd7570415578ba2da67e056d8f89b420d2e5b82412dc0f0e02877b9e48c",
366 "expires_at": 0
367 });
368
369 let req: VerifyChallengeRequest = serde_json::from_value(data).unwrap();
370 let result = verify_challenge(
371 &req.challenge,
372 &req.signature,
373 &req.challenge_request.pub_key,
374 req.challenge_request.key_type,
375 );
376 assert!(
377 result.is_ok(),
378 "Failed to verify Sr25519 challenge: {}",
379 result.err().unwrap()
380 );
381 assert!(result.is_ok(), "Sr25519 verification failed");
382 }
383}