1use crate::did::DidDocument;
12use crate::proof::ed25519::{Ed25519Signer, Ed25519Verifier};
13use crate::proof::ProofPurpose;
14use crate::{DidError, DidResult, VerificationMethod};
15use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
16use serde::{Deserialize, Serialize};
17use sha2::{Digest, Sha256};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum JwsAlgorithm {
22 #[serde(rename = "EdDSA")]
24 EdDsa,
25 #[serde(rename = "ES256")]
27 Es256,
28 #[serde(rename = "ES256K")]
30 Es256K,
31 #[serde(rename = "RS256")]
33 Rs256,
34}
35
36impl JwsAlgorithm {
37 pub fn as_str(&self) -> &'static str {
39 match self {
40 Self::EdDsa => "EdDSA",
41 Self::Es256 => "ES256",
42 Self::Es256K => "ES256K",
43 Self::Rs256 => "RS256",
44 }
45 }
46
47 pub fn parse(s: &str) -> DidResult<Self> {
49 match s {
50 "EdDSA" => Ok(Self::EdDsa),
51 "ES256" => Ok(Self::Es256),
52 "ES256K" => Ok(Self::Es256K),
53 "RS256" => Ok(Self::Rs256),
54 other => Err(DidError::InvalidKey(format!(
55 "Unknown JWS algorithm: {}",
56 other
57 ))),
58 }
59 }
60
61 pub fn signature_length(&self) -> Option<usize> {
63 match self {
64 Self::EdDsa => Some(64), Self::Es256 => Some(64), Self::Es256K => Some(64), Self::Rs256 => None, }
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct JwsHeader {
75 pub alg: JwsAlgorithm,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub kid: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
83 pub b64: Option<bool>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub crit: Option<Vec<String>>,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub cty: Option<String>,
90}
91
92impl JwsHeader {
93 pub fn ed_dsa(kid: Option<&str>) -> Self {
95 Self {
96 alg: JwsAlgorithm::EdDsa,
97 kid: kid.map(String::from),
98 b64: None,
99 crit: None,
100 cty: None,
101 }
102 }
103
104 pub fn detached(alg: JwsAlgorithm, kid: Option<&str>) -> Self {
107 Self {
108 alg,
109 kid: kid.map(String::from),
110 b64: Some(false),
111 crit: Some(vec!["b64".to_string()]),
112 cty: None,
113 }
114 }
115
116 pub fn encode(&self) -> DidResult<String> {
118 let json = serde_json::to_string(self)
119 .map_err(|e| DidError::SerializationError(format!("Header serialize: {}", e)))?;
120 Ok(URL_SAFE_NO_PAD.encode(json.as_bytes()))
121 }
122
123 pub fn decode(encoded: &str) -> DidResult<Self> {
125 let bytes = URL_SAFE_NO_PAD
126 .decode(encoded)
127 .map_err(|e| DidError::InvalidProof(format!("Header base64 decode: {}", e)))?;
128 serde_json::from_slice(&bytes)
129 .map_err(|e| DidError::SerializationError(format!("Header deserialize: {}", e)))
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct CompactJws {
136 pub header_b64: String,
138 pub payload_b64: String,
140 pub signature_b64: String,
142}
143
144impl CompactJws {
145 pub fn parse(jws: &str) -> DidResult<Self> {
147 let parts: Vec<&str> = jws.split('.').collect();
148 if parts.len() != 3 {
149 return Err(DidError::InvalidProof(format!(
150 "JWS must have 3 parts separated by '.', got {}",
151 parts.len()
152 )));
153 }
154
155 Ok(Self {
156 header_b64: parts[0].to_string(),
157 payload_b64: parts[1].to_string(),
158 signature_b64: parts[2].to_string(),
159 })
160 }
161
162 pub fn to_detached_string(&self) -> String {
164 format!("{}..{}", self.header_b64, self.signature_b64)
165 }
166
167 pub fn signing_input(&self) -> Vec<u8> {
169 format!("{}.{}", self.header_b64, self.payload_b64).into_bytes()
170 }
171
172 pub fn signature_bytes(&self) -> DidResult<Vec<u8>> {
174 URL_SAFE_NO_PAD
175 .decode(&self.signature_b64)
176 .map_err(|e| DidError::InvalidProof(format!("Signature base64 decode: {}", e)))
177 }
178
179 pub fn header(&self) -> DidResult<JwsHeader> {
181 JwsHeader::decode(&self.header_b64)
182 }
183}
184
185impl std::fmt::Display for CompactJws {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 write!(
188 f,
189 "{}.{}.{}",
190 self.header_b64, self.payload_b64, self.signature_b64
191 )
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct JsonWebSignature2020 {
201 #[serde(rename = "type")]
203 pub proof_type: String,
204 pub created: String,
206 pub verification_method: String,
208 pub proof_purpose: String,
210 pub jws: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub challenge: Option<String>,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub domain: Option<String>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub nonce: Option<String>,
221}
222
223impl JsonWebSignature2020 {
224 pub const PROOF_TYPE: &'static str = "JsonWebSignature2020";
226
227 pub fn sign(
236 document: &serde_json::Value,
237 secret_key_bytes: &[u8],
238 vm_id: &str,
239 purpose: ProofPurpose,
240 ) -> DidResult<Self> {
241 let doc_bytes = serialize_document(document)?;
243
244 let doc_hash = sha256_hash(&doc_bytes);
246
247 let header = JwsHeader::detached(JwsAlgorithm::EdDsa, Some(vm_id));
249 let header_b64 = header.encode()?;
250
251 let payload_b64 = URL_SAFE_NO_PAD.encode(&doc_hash);
253
254 let signing_input = format!("{}.{}", header_b64, payload_b64).into_bytes();
256
257 let signer = Ed25519Signer::from_bytes(secret_key_bytes)?;
259 let signature = signer.sign(&signing_input);
260 let signature_b64 = URL_SAFE_NO_PAD.encode(&signature);
261
262 let jws = format!("{}..{}", header_b64, signature_b64);
264
265 Ok(Self {
266 proof_type: Self::PROOF_TYPE.to_string(),
267 created: chrono::Utc::now().to_rfc3339(),
268 verification_method: vm_id.to_string(),
269 proof_purpose: purpose.as_str().to_string(),
270 jws,
271 challenge: None,
272 domain: None,
273 nonce: None,
274 })
275 }
276
277 pub fn verify(
284 document: &serde_json::Value,
285 proof: &Self,
286 public_key_bytes: &[u8],
287 ) -> DidResult<bool> {
288 let jws_str = &proof.jws;
290
291 let (header_b64, signature_b64) = parse_jws_parts(jws_str)?;
293
294 let header = JwsHeader::decode(&header_b64)?;
296
297 let doc_bytes = serialize_document(document)?;
299 let doc_hash = sha256_hash(&doc_bytes);
300 let payload_b64 = URL_SAFE_NO_PAD.encode(&doc_hash);
301
302 let signing_input = format!("{}.{}", header_b64, payload_b64).into_bytes();
304
305 let signature = URL_SAFE_NO_PAD
307 .decode(&signature_b64)
308 .map_err(|e| DidError::InvalidProof(format!("Signature decode error: {}", e)))?;
309
310 match header.alg {
312 JwsAlgorithm::EdDsa => {
313 let verifier = Ed25519Verifier::from_bytes(public_key_bytes)?;
314 verifier.verify(&signing_input, &signature)
315 }
316 other => Err(DidError::InvalidProof(format!(
317 "Algorithm {:?} verification not yet implemented",
318 other
319 ))),
320 }
321 }
322
323 pub fn verify_with_method(
325 document: &serde_json::Value,
326 proof: &Self,
327 vm: &VerificationMethod,
328 ) -> DidResult<bool> {
329 let public_key = vm.get_public_key_bytes()?;
330 Self::verify(document, proof, &public_key)
331 }
332
333 pub fn with_challenge(mut self, challenge: &str) -> Self {
335 self.challenge = Some(challenge.to_string());
336 self
337 }
338
339 pub fn with_domain(mut self, domain: &str) -> Self {
341 self.domain = Some(domain.to_string());
342 self
343 }
344
345 pub fn with_nonce(mut self, nonce: &str) -> Self {
347 self.nonce = Some(nonce.to_string());
348 self
349 }
350
351 pub fn to_proof(&self) -> crate::proof::Proof {
353 crate::proof::Proof {
354 proof_type: Self::PROOF_TYPE.to_string(),
355 created: chrono::DateTime::parse_from_rfc3339(&self.created)
356 .map(|dt| dt.with_timezone(&chrono::Utc))
357 .unwrap_or_else(|_| chrono::Utc::now()),
358 verification_method: self.verification_method.clone(),
359 proof_purpose: self.proof_purpose.clone(),
360 proof_value: None,
361 jws: Some(self.jws.clone()),
362 challenge: self.challenge.clone(),
363 domain: self.domain.clone(),
364 nonce: self.nonce.clone(),
365 cryptosuite: None,
366 }
367 }
368}
369
370pub struct JwsSigner {
372 algorithm: JwsAlgorithm,
373 secret_key: Vec<u8>,
374 key_id: Option<String>,
375}
376
377impl JwsSigner {
378 pub fn ed_dsa(secret_key: &[u8], key_id: Option<&str>) -> DidResult<Self> {
380 if secret_key.len() != 32 {
381 return Err(DidError::InvalidKey(
382 "Ed25519 secret key must be 32 bytes".to_string(),
383 ));
384 }
385 Ok(Self {
386 algorithm: JwsAlgorithm::EdDsa,
387 secret_key: secret_key.to_vec(),
388 key_id: key_id.map(String::from),
389 })
390 }
391
392 pub fn sign(&self, payload: &[u8]) -> DidResult<CompactJws> {
394 let header = JwsHeader {
395 alg: self.algorithm,
396 kid: self.key_id.clone(),
397 b64: None,
398 crit: None,
399 cty: None,
400 };
401
402 let header_b64 = header.encode()?;
403 let payload_b64 = URL_SAFE_NO_PAD.encode(payload);
404 let signing_input = format!("{}.{}", header_b64, payload_b64).into_bytes();
405
406 let signature_b64 = match self.algorithm {
407 JwsAlgorithm::EdDsa => {
408 let signer = Ed25519Signer::from_bytes(&self.secret_key)?;
409 let sig = signer.sign(&signing_input);
410 URL_SAFE_NO_PAD.encode(&sig)
411 }
412 other => {
413 return Err(DidError::SigningFailed(format!(
414 "Algorithm {:?} signing not yet implemented",
415 other
416 )));
417 }
418 };
419
420 Ok(CompactJws {
421 header_b64,
422 payload_b64,
423 signature_b64,
424 })
425 }
426}
427
428pub struct JwsVerifier {
430 public_key: Vec<u8>,
431}
432
433impl JwsVerifier {
434 pub fn ed25519(public_key: &[u8]) -> DidResult<Self> {
436 if public_key.len() != 32 {
437 return Err(DidError::InvalidKey(
438 "Ed25519 public key must be 32 bytes".to_string(),
439 ));
440 }
441 Ok(Self {
442 public_key: public_key.to_vec(),
443 })
444 }
445
446 pub fn verify_compact(&self, jws: &CompactJws) -> DidResult<bool> {
448 let header = jws.header()?;
449 let signing_input = jws.signing_input();
450 let signature = jws.signature_bytes()?;
451
452 match header.alg {
453 JwsAlgorithm::EdDsa => {
454 let verifier = Ed25519Verifier::from_bytes(&self.public_key)?;
455 verifier.verify(&signing_input, &signature)
456 }
457 other => Err(DidError::VerificationFailed(format!(
458 "Algorithm {:?} verification not yet implemented",
459 other
460 ))),
461 }
462 }
463
464 pub fn verify_string(&self, jws_str: &str) -> DidResult<bool> {
466 let jws = CompactJws::parse(jws_str)?;
467 self.verify_compact(&jws)
468 }
469}
470
471fn parse_jws_parts(jws_str: &str) -> DidResult<(String, String)> {
474 let parts: Vec<&str> = jws_str.split('.').collect();
475
476 match parts.len() {
477 3 => {
478 Ok((parts[0].to_string(), parts[2].to_string()))
480 }
481 _ => Err(DidError::InvalidProof(format!(
482 "Invalid JWS format: expected 3 dot-separated parts, got {}",
483 parts.len()
484 ))),
485 }
486}
487
488fn serialize_document(doc: &serde_json::Value) -> DidResult<Vec<u8>> {
490 let canonical = canonicalize_json(doc)?;
494 Ok(canonical.into_bytes())
495}
496
497fn canonicalize_json(value: &serde_json::Value) -> DidResult<String> {
499 match value {
500 serde_json::Value::Object(map) => {
501 let mut sorted: Vec<(&String, &serde_json::Value)> = map.iter().collect();
502 sorted.sort_by_key(|(k, _)| k.as_str());
503
504 let mut parts = Vec::with_capacity(sorted.len());
505 for (k, v) in sorted {
506 let key = serde_json::to_string(k)
507 .map_err(|e| DidError::SerializationError(e.to_string()))?;
508 let val = canonicalize_json(v)?;
509 parts.push(format!("{}:{}", key, val));
510 }
511 Ok(format!("{{{}}}", parts.join(",")))
512 }
513 serde_json::Value::Array(arr) => {
514 let parts: DidResult<Vec<String>> = arr.iter().map(canonicalize_json).collect();
515 Ok(format!("[{}]", parts?.join(",")))
516 }
517 other => {
518 serde_json::to_string(other).map_err(|e| DidError::SerializationError(e.to_string()))
519 }
520 }
521}
522
523fn sha256_hash(data: &[u8]) -> Vec<u8> {
525 let mut hasher = Sha256::new();
526 hasher.update(data);
527 hasher.finalize().to_vec()
528}
529
530pub fn attach_jws_proof(
532 document: &mut serde_json::Value,
533 proof: &JsonWebSignature2020,
534) -> DidResult<()> {
535 let proof_value =
536 serde_json::to_value(proof).map_err(|e| DidError::SerializationError(e.to_string()))?;
537
538 if let Some(obj) = document.as_object_mut() {
539 obj.insert("proof".to_string(), proof_value);
540 Ok(())
541 } else {
542 Err(DidError::InvalidFormat(
543 "Document must be a JSON object".to_string(),
544 ))
545 }
546}
547
548pub fn extract_jws_proof(document: &mut serde_json::Value) -> DidResult<JsonWebSignature2020> {
550 if let Some(obj) = document.as_object_mut() {
551 let proof_value = obj
552 .remove("proof")
553 .ok_or_else(|| DidError::InvalidProof("Document has no 'proof' field".to_string()))?;
554
555 serde_json::from_value(proof_value)
556 .map_err(|e| DidError::SerializationError(format!("Proof deserialize: {}", e)))
557 } else {
558 Err(DidError::InvalidFormat(
559 "Document must be a JSON object".to_string(),
560 ))
561 }
562}
563
564pub fn sign_document(
566 document: &mut serde_json::Value,
567 secret_key_bytes: &[u8],
568 vm_id: &str,
569 purpose: ProofPurpose,
570) -> DidResult<()> {
571 let proof = JsonWebSignature2020::sign(document, secret_key_bytes, vm_id, purpose)?;
573 attach_jws_proof(document, &proof)
574}
575
576pub fn verify_document(
578 document: &mut serde_json::Value,
579 public_key_bytes: &[u8],
580) -> DidResult<bool> {
581 let proof = extract_jws_proof(document)?;
583
584 JsonWebSignature2020::verify(document, &proof, public_key_bytes)
586}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591 use crate::proof::ed25519::Ed25519Signer;
592
593 fn generate_test_keypair() -> (Vec<u8>, Vec<u8>) {
594 let signer = Ed25519Signer::generate();
595 (
596 signer.secret_key_bytes().to_vec(),
597 signer.public_key_bytes().to_vec(),
598 )
599 }
600
601 #[test]
602 fn test_jws_header_encode_decode() {
603 let header = JwsHeader::ed_dsa(Some("did:key:z123#key-1"));
604 let encoded = header.encode().unwrap();
605 let decoded = JwsHeader::decode(&encoded).unwrap();
606 assert_eq!(decoded.alg, JwsAlgorithm::EdDsa);
607 assert_eq!(decoded.kid, Some("did:key:z123#key-1".to_string()));
608 }
609
610 #[test]
611 fn test_detached_jws_header() {
612 let header = JwsHeader::detached(JwsAlgorithm::EdDsa, Some("kid"));
613 assert_eq!(header.b64, Some(false));
614 assert!(header.crit.is_some());
615 assert!(header.crit.as_ref().unwrap().contains(&"b64".to_string()));
616 }
617
618 #[test]
619 fn test_compact_jws_sign_verify() {
620 let (secret, public) = generate_test_keypair();
621 let payload = b"Hello, World!";
622
623 let signer = JwsSigner::ed_dsa(&secret, Some("test-key")).unwrap();
624 let compact = signer.sign(payload).unwrap();
625
626 let jws_string = compact.to_string();
627 assert_eq!(jws_string.split('.').count(), 3);
628
629 let verifier = JwsVerifier::ed25519(&public).unwrap();
630 let valid = verifier.verify_string(&jws_string).unwrap();
631 assert!(valid);
632 }
633
634 #[test]
635 fn test_compact_jws_invalid_signature() {
636 let (secret, _) = generate_test_keypair();
637 let (_, other_public) = generate_test_keypair();
638 let payload = b"Hello, World!";
639
640 let signer = JwsSigner::ed_dsa(&secret, None).unwrap();
641 let compact = signer.sign(payload).unwrap();
642
643 let verifier = JwsVerifier::ed25519(&other_public).unwrap();
644 let valid = verifier.verify_compact(&compact).unwrap();
645 assert!(!valid);
646 }
647
648 #[test]
649 fn test_json_web_signature_2020_sign_verify() {
650 let (secret, public) = generate_test_keypair();
651 let document = serde_json::json!({
652 "@context": ["https://www.w3.org/2018/credentials/v1"],
653 "type": ["VerifiableCredential"],
654 "issuer": "did:key:z6Mk",
655 "credentialSubject": {
656 "id": "did:example:alice",
657 "name": "Alice"
658 }
659 });
660
661 let vm_id = "did:key:z6Mk#key-1";
662 let proof =
663 JsonWebSignature2020::sign(&document, &secret, vm_id, ProofPurpose::AssertionMethod)
664 .unwrap();
665
666 assert_eq!(proof.proof_type, JsonWebSignature2020::PROOF_TYPE);
667 assert_eq!(proof.verification_method, vm_id);
668 assert!(proof.jws.contains(".."));
669
670 let valid = JsonWebSignature2020::verify(&document, &proof, &public).unwrap();
671 assert!(valid);
672 }
673
674 #[test]
675 fn test_jws_tampered_document() {
676 let (secret, public) = generate_test_keypair();
677 let document = serde_json::json!({
678 "name": "Alice",
679 "age": 30
680 });
681
682 let proof = JsonWebSignature2020::sign(
683 &document,
684 &secret,
685 "did:key:z123#key-1",
686 ProofPurpose::AssertionMethod,
687 )
688 .unwrap();
689
690 let tampered = serde_json::json!({
692 "name": "Bob", "age": 30
694 });
695
696 let valid = JsonWebSignature2020::verify(&tampered, &proof, &public).unwrap();
697 assert!(!valid, "Tampered document should not verify");
698 }
699
700 #[test]
701 fn test_sign_verify_document_end_to_end() {
702 let (secret, public) = generate_test_keypair();
703 let mut document = serde_json::json!({
704 "@context": ["https://www.w3.org/2018/credentials/v1"],
705 "type": "TestDocument",
706 "subject": "test"
707 });
708
709 sign_document(
710 &mut document,
711 &secret,
712 "did:key:z6Mk#key-1",
713 ProofPurpose::AssertionMethod,
714 )
715 .unwrap();
716
717 assert!(document.get("proof").is_some());
718
719 let valid = verify_document(&mut document, &public).unwrap();
720 assert!(valid);
721 }
722
723 #[test]
724 fn test_canonicalize_json_deterministic() {
725 let v1 = serde_json::json!({"b": 2, "a": 1});
727 let v2 = serde_json::json!({"a": 1, "b": 2});
728
729 let c1 = canonicalize_json(&v1).unwrap();
730 let c2 = canonicalize_json(&v2).unwrap();
731
732 assert_eq!(c1, c2);
733 }
734
735 #[test]
736 fn test_jws_algorithm_roundtrip() {
737 for alg in [
738 JwsAlgorithm::EdDsa,
739 JwsAlgorithm::Es256,
740 JwsAlgorithm::Es256K,
741 JwsAlgorithm::Rs256,
742 ] {
743 let s = alg.as_str();
744 let parsed = JwsAlgorithm::parse(s).unwrap();
745 assert_eq!(parsed, alg);
746 }
747 }
748
749 #[test]
750 fn test_proof_to_crate_proof() {
751 let (secret, _) = generate_test_keypair();
752 let document = serde_json::json!({"test": true});
753
754 let jws_proof = JsonWebSignature2020::sign(
755 &document,
756 &secret,
757 "did:key:z#key-1",
758 ProofPurpose::AssertionMethod,
759 )
760 .unwrap();
761
762 let proof = jws_proof.to_proof();
763 assert_eq!(proof.proof_type, JsonWebSignature2020::PROOF_TYPE);
764 assert!(proof.jws.is_some());
765 }
766}