1use crate::error::{Error, Result};
36use crate::ipld::Ipld;
37use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
38use serde::{Deserialize, Serialize};
39use std::collections::BTreeMap;
40
41fn pem_to_der(pem: &[u8]) -> Result<Vec<u8>> {
44 let pem_str = std::str::from_utf8(pem)
45 .map_err(|e| Error::InvalidInput(format!("Invalid UTF-8 in PEM: {}", e)))?;
46
47 let lines: Vec<&str> = pem_str
49 .lines()
50 .filter(|line| !line.starts_with("-----"))
51 .collect();
52
53 let base64_content = lines.join("");
54
55 use base64::{engine::general_purpose::STANDARD, Engine as _};
57 STANDARD
58 .decode(base64_content.as_bytes())
59 .map_err(|e| Error::InvalidInput(format!("Failed to decode base64 in PEM: {}", e)))
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub struct JoseSignature {
68 pub payload: Ipld,
70 pub signature: String,
72 pub algorithm: String,
74}
75
76pub struct JoseBuilder {
80 payload: Option<Ipld>,
81}
82
83impl JoseBuilder {
84 pub fn new() -> Self {
86 Self { payload: None }
87 }
88
89 pub fn with_payload(mut self, payload: Ipld) -> Self {
91 self.payload = Some(payload);
92 self
93 }
94
95 pub fn sign_hs256(self, secret: &[u8]) -> Result<JoseSignature> {
103 let payload = self
104 .payload
105 .ok_or_else(|| Error::InvalidInput("No payload set".to_string()))?;
106
107 if secret.len() < 32 {
108 return Err(Error::InvalidInput(
109 "HMAC secret must be at least 32 bytes".to_string(),
110 ));
111 }
112
113 let json_payload = ipld_to_json_value(&payload)?;
115
116 let claims = serde_json::json!({
118 "payload": json_payload,
119 });
120
121 let header = Header::new(Algorithm::HS256);
123 let token = encode(&header, &claims, &EncodingKey::from_secret(secret))
124 .map_err(|e| Error::Serialization(format!("Failed to sign data: {}", e)))?;
125
126 Ok(JoseSignature {
127 payload,
128 signature: token,
129 algorithm: "HS256".to_string(),
130 })
131 }
132
133 pub fn sign_rs256(self, private_key_pem: &[u8]) -> Result<JoseSignature> {
141 let payload = self
142 .payload
143 .ok_or_else(|| Error::InvalidInput("No payload set".to_string()))?;
144
145 let json_payload = ipld_to_json_value(&payload)?;
147
148 let claims = serde_json::json!({
150 "payload": json_payload,
151 });
152
153 let header = Header::new(Algorithm::RS256);
155 let der = pem_to_der(private_key_pem)?;
156 let token = encode(&header, &claims, &EncodingKey::from_rsa_der(&der))
157 .map_err(|e| Error::Serialization(format!("Failed to sign data: {}", e)))?;
158
159 Ok(JoseSignature {
160 payload,
161 signature: token,
162 algorithm: "RS256".to_string(),
163 })
164 }
165}
166
167impl Default for JoseBuilder {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl JoseSignature {
174 pub fn verify_hs256(&self, secret: &[u8]) -> Result<bool> {
182 if self.algorithm != "HS256" {
183 return Err(Error::InvalidInput(format!(
184 "Expected HS256 algorithm, got {}",
185 self.algorithm
186 )));
187 }
188
189 let mut validation = Validation::new(Algorithm::HS256);
191 validation.required_spec_claims.clear();
193 validation.validate_exp = false;
194 validation.validate_nbf = false;
195
196 let token_data = decode::<serde_json::Value>(
197 &self.signature,
198 &DecodingKey::from_secret(secret),
199 &validation,
200 );
201
202 match token_data {
203 Ok(_) => Ok(true),
204 Err(e) => {
205 match e.kind() {
207 jsonwebtoken::errors::ErrorKind::InvalidSignature => Ok(false),
208 _ => Err(Error::Verification(format!(
209 "Failed to verify signature: {}",
210 e
211 ))),
212 }
213 }
214 }
215 }
216
217 pub fn verify_rs256(&self, public_key_pem: &[u8]) -> Result<bool> {
225 if self.algorithm != "RS256" {
226 return Err(Error::InvalidInput(format!(
227 "Expected RS256 algorithm, got {}",
228 self.algorithm
229 )));
230 }
231
232 let mut validation = Validation::new(Algorithm::RS256);
234 validation.required_spec_claims.clear();
236 validation.validate_exp = false;
237 validation.validate_nbf = false;
238
239 let der = pem_to_der(public_key_pem)?;
240 let token_data = decode::<serde_json::Value>(
241 &self.signature,
242 &DecodingKey::from_rsa_der(&der),
243 &validation,
244 );
245
246 match token_data {
247 Ok(_) => Ok(true),
248 Err(e) => match e.kind() {
249 jsonwebtoken::errors::ErrorKind::InvalidSignature => Ok(false),
250 _ => Err(Error::Verification(format!(
251 "Failed to verify signature: {}",
252 e
253 ))),
254 },
255 }
256 }
257
258 pub fn to_dag_jose(&self) -> Result<Vec<u8>> {
262 let jose_object = serde_json::json!({
263 "payload": ipld_to_json_value(&self.payload)?,
264 "signatures": [{
265 "protected": self.algorithm,
266 "signature": self.signature,
267 }]
268 });
269
270 serde_json::to_vec(&jose_object)
271 .map_err(|e| Error::Serialization(format!("Failed to serialize DAG-JOSE: {}", e)))
272 }
273
274 pub fn from_dag_jose(data: &[u8]) -> Result<Self> {
278 let jose_object: serde_json::Value = serde_json::from_slice(data)
279 .map_err(|e| Error::Deserialization(format!("Failed to parse DAG-JOSE: {}", e)))?;
280
281 let payload_json = jose_object
282 .get("payload")
283 .ok_or_else(|| Error::Deserialization("Missing payload field".to_string()))?;
284
285 let signatures = jose_object
286 .get("signatures")
287 .and_then(|s| s.as_array())
288 .ok_or_else(|| Error::Deserialization("Missing or invalid signatures".to_string()))?;
289
290 if signatures.is_empty() {
291 return Err(Error::Deserialization("No signatures found".to_string()));
292 }
293
294 let first_sig = &signatures[0];
295 let algorithm = first_sig
296 .get("protected")
297 .and_then(|a| a.as_str())
298 .ok_or_else(|| Error::Deserialization("Missing algorithm".to_string()))?
299 .to_string();
300
301 let signature = first_sig
302 .get("signature")
303 .and_then(|s| s.as_str())
304 .ok_or_else(|| Error::Deserialization("Missing signature".to_string()))?
305 .to_string();
306
307 let payload = json_value_to_ipld(payload_json)?;
308
309 Ok(JoseSignature {
310 payload,
311 signature,
312 algorithm,
313 })
314 }
315}
316
317fn ipld_to_json_value(ipld: &Ipld) -> Result<serde_json::Value> {
319 match ipld {
320 Ipld::Null => Ok(serde_json::Value::Null),
321 Ipld::Bool(b) => Ok(serde_json::Value::Bool(*b)),
322 Ipld::Integer(i) => {
323 let i64_val: i64 = (*i)
325 .try_into()
326 .map_err(|_| Error::Serialization("Integer value out of i64 range".to_string()))?;
327 Ok(serde_json::Value::Number(i64_val.into()))
328 }
329 Ipld::Float(f) => serde_json::Number::from_f64(*f)
330 .map(serde_json::Value::Number)
331 .ok_or_else(|| Error::Serialization("Invalid float value".to_string())),
332 Ipld::String(s) => Ok(serde_json::Value::String(s.clone())),
333 Ipld::Bytes(b) => {
334 let encoded = base64_encode(b);
336 Ok(serde_json::json!({
337 "/": {
338 "bytes": encoded
339 }
340 }))
341 }
342 Ipld::List(list) => {
343 let values: Result<Vec<_>> = list.iter().map(ipld_to_json_value).collect();
344 Ok(serde_json::Value::Array(values?))
345 }
346 Ipld::Map(map) => {
347 let mut json_map = serde_json::Map::new();
348 for (k, v) in map {
349 json_map.insert(k.clone(), ipld_to_json_value(v)?);
350 }
351 Ok(serde_json::Value::Object(json_map))
352 }
353 Ipld::Link(cid) => {
354 Ok(serde_json::json!({
356 "/": cid.to_string()
357 }))
358 }
359 }
360}
361
362fn json_value_to_ipld(value: &serde_json::Value) -> Result<Ipld> {
364 match value {
365 serde_json::Value::Null => Ok(Ipld::Null),
366 serde_json::Value::Bool(b) => Ok(Ipld::Bool(*b)),
367 serde_json::Value::Number(n) => {
368 if let Some(i) = n.as_i64() {
369 Ok(Ipld::Integer(i as i128))
370 } else if let Some(f) = n.as_f64() {
371 Ok(Ipld::Float(f))
372 } else {
373 Err(Error::Deserialization("Invalid number".to_string()))
374 }
375 }
376 serde_json::Value::String(s) => Ok(Ipld::String(s.clone())),
377 serde_json::Value::Array(arr) => {
378 let items: Result<Vec<_>> = arr.iter().map(json_value_to_ipld).collect();
379 Ok(Ipld::List(items?))
380 }
381 serde_json::Value::Object(obj) => {
382 if obj.len() == 1 && obj.contains_key("/") {
384 let special = obj.get("/").unwrap();
385
386 if let Some(bytes_obj) = special.as_object() {
388 if bytes_obj.len() == 1 && bytes_obj.contains_key("bytes") {
389 if let Some(b64_str) = bytes_obj.get("bytes").and_then(|v| v.as_str()) {
390 return Ok(Ipld::Bytes(base64_decode(b64_str)?));
393 }
394 }
395 }
396
397 if let Some(cid_str) = special.as_str() {
399 let cid = crate::cid::parse_cid(cid_str)?;
400 return Ok(Ipld::Link(crate::cid::SerializableCid(cid)));
401 }
402 }
403
404 let mut map = BTreeMap::new();
406 for (k, v) in obj {
407 map.insert(k.clone(), json_value_to_ipld(v)?);
408 }
409 Ok(Ipld::Map(map))
410 }
411 }
412}
413
414fn base64_encode(data: &[u8]) -> String {
416 use std::fmt::Write;
417 let mut result = String::new();
418 for chunk in data.chunks(3) {
419 let b1 = chunk[0];
420 let b2 = chunk.get(1).copied().unwrap_or(0);
421 let b3 = chunk.get(2).copied().unwrap_or(0);
422
423 let n = ((b1 as u32) << 16) | ((b2 as u32) << 8) | (b3 as u32);
424
425 let chars = [
426 b64char((n >> 18) & 0x3f),
427 b64char((n >> 12) & 0x3f),
428 if chunk.len() > 1 {
429 b64char((n >> 6) & 0x3f)
430 } else {
431 '='
432 },
433 if chunk.len() > 2 {
434 b64char(n & 0x3f)
435 } else {
436 '='
437 },
438 ];
439
440 for c in &chars {
441 write!(&mut result, "{}", c).unwrap();
442 }
443 }
444 result
445}
446
447fn b64char(n: u32) -> char {
448 const CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
449 CHARS[n as usize] as char
450}
451
452fn base64_decode(s: &str) -> Result<Vec<u8>> {
454 if s.is_empty() {
455 return Ok(Vec::new());
456 }
457
458 let bytes = s.as_bytes();
459 let mut result = Vec::new();
460
461 for chunk in bytes.chunks(4) {
462 if chunk.len() < 2 {
463 break;
464 }
465
466 let c0 = b64decode_char(chunk[0])?;
467 let c1 = b64decode_char(chunk[1])?;
468 let c2 = if chunk.len() > 2 && chunk[2] != b'=' {
469 b64decode_char(chunk[2])?
470 } else {
471 0
472 };
473 let c3 = if chunk.len() > 3 && chunk[3] != b'=' {
474 b64decode_char(chunk[3])?
475 } else {
476 0
477 };
478
479 result.push((c0 << 2) | (c1 >> 4));
480 if chunk.len() > 2 && chunk[2] != b'=' {
481 result.push((c1 << 4) | (c2 >> 2));
482 }
483 if chunk.len() > 3 && chunk[3] != b'=' {
484 result.push((c2 << 6) | c3);
485 }
486 }
487
488 Ok(result)
489}
490
491fn b64decode_char(c: u8) -> Result<u8> {
492 match c {
493 b'A'..=b'Z' => Ok(c - b'A'),
494 b'a'..=b'z' => Ok(c - b'a' + 26),
495 b'0'..=b'9' => Ok(c - b'0' + 52),
496 b'+' => Ok(62),
497 b'/' => Ok(63),
498 _ => Err(Error::Deserialization(format!(
499 "Invalid base64 character: {}",
500 c
501 ))),
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_jose_sign_verify_hs256() {
511 let data = Ipld::String("Hello, DAG-JOSE!".to_string());
512 let secret = b"my-secret-key-must-be-32-bytes!!";
513
514 let jose = JoseBuilder::new()
516 .with_payload(data.clone())
517 .sign_hs256(secret)
518 .unwrap();
519
520 assert_eq!(jose.payload, data);
521 assert_eq!(jose.algorithm, "HS256");
522
523 assert!(jose.verify_hs256(secret).unwrap());
525
526 let wrong_secret = b"wrong-secret-key-must-be-32byte!";
528 assert!(!jose.verify_hs256(wrong_secret).unwrap());
529 }
530
531 #[test]
532 fn test_jose_sign_different_payloads() {
533 let secret = b"my-secret-key-must-be-32-bytes!!";
534
535 let jose1 = JoseBuilder::new()
537 .with_payload(Ipld::String("payload1".to_string()))
538 .sign_hs256(secret)
539 .unwrap();
540
541 let jose2 = JoseBuilder::new()
542 .with_payload(Ipld::String("payload2".to_string()))
543 .sign_hs256(secret)
544 .unwrap();
545
546 assert_ne!(jose1.signature, jose2.signature);
548 }
549
550 #[test]
551 fn test_jose_with_complex_ipld() {
552 let mut map = BTreeMap::new();
553 map.insert("name".to_string(), Ipld::String("Alice".to_string()));
554 map.insert("age".to_string(), Ipld::Integer(30));
555 map.insert(
556 "roles".to_string(),
557 Ipld::List(vec![
558 Ipld::String("admin".to_string()),
559 Ipld::String("user".to_string()),
560 ]),
561 );
562
563 let data = Ipld::Map(map);
564 let secret = b"my-secret-key-must-be-32-bytes!!";
565
566 let jose = JoseBuilder::new()
567 .with_payload(data.clone())
568 .sign_hs256(secret)
569 .unwrap();
570
571 assert_eq!(jose.payload, data);
572 assert!(jose.verify_hs256(secret).unwrap());
573 }
574
575 #[test]
576 fn test_jose_short_secret_fails() {
577 let data = Ipld::String("test".to_string());
578 let short_secret = b"short"; let result = JoseBuilder::new()
581 .with_payload(data)
582 .sign_hs256(short_secret);
583
584 assert!(result.is_err());
585 }
586
587 #[test]
588 fn test_jose_no_payload_fails() {
589 let secret = b"my-secret-key-must-be-32-bytes!!";
590
591 let result = JoseBuilder::new().sign_hs256(secret);
592
593 assert!(result.is_err());
594 }
595
596 #[test]
597 fn test_jose_to_dag_jose() {
598 let data = Ipld::String("Hello".to_string());
599 let secret = b"my-secret-key-must-be-32-bytes!!";
600
601 let jose = JoseBuilder::new()
602 .with_payload(data)
603 .sign_hs256(secret)
604 .unwrap();
605
606 let dag_jose = jose.to_dag_jose().unwrap();
608
609 let parsed: serde_json::Value = serde_json::from_slice(&dag_jose).unwrap();
611 assert!(parsed.get("payload").is_some());
612 assert!(parsed.get("signatures").is_some());
613 }
614
615 #[test]
616 fn test_jose_roundtrip_dag_jose() {
617 let data = Ipld::String("Roundtrip test".to_string());
618 let secret = b"my-secret-key-must-be-32-bytes!!";
619
620 let jose = JoseBuilder::new()
621 .with_payload(data.clone())
622 .sign_hs256(secret)
623 .unwrap();
624
625 let dag_jose = jose.to_dag_jose().unwrap();
627
628 let decoded = JoseSignature::from_dag_jose(&dag_jose).unwrap();
630
631 assert_eq!(decoded.payload, data);
632 assert_eq!(decoded.algorithm, jose.algorithm);
633 assert!(decoded.verify_hs256(secret).unwrap());
634 }
635
636 #[test]
637 fn test_base64_encode() {
638 let data = b"hello world";
639 let encoded = base64_encode(data);
640 assert!(!encoded.is_empty());
642 assert!(encoded
643 .chars()
644 .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '='));
645 }
646}