diff --git a/crates/credentials/src/vc.rs b/crates/credentials/src/vc.rs index 920698f7..9e9d7579 100644 --- a/crates/credentials/src/vc.rs +++ b/crates/credentials/src/vc.rs @@ -1,9 +1,9 @@ use core::fmt; use dids::{bearer::BearerDid, document::KeySelector}; -use jws::v2::JwsError; +use jws::JwsError; use jwt::{ jws::Jwt, - lib_v2::{Claims, JwtError, RegisteredClaims}, + {Claims, JwtError, RegisteredClaims}, }; use serde::{Deserialize, Serialize}; use std::{ diff --git a/crates/jws/src/lib.rs b/crates/jws/src/lib.rs index da0d3c2c..ac9b5896 100644 --- a/crates/jws/src/lib.rs +++ b/crates/jws/src/lib.rs @@ -1,6 +1,4 @@ -pub mod v2; - -use base64::{engine::general_purpose, Engine as _}; +use base64::{engine::general_purpose, DecodeError, Engine as _}; use crypto::{ed25519::Ed25519, secp256k1::Secp256k1, CryptoError, CurveOperations}; use dids::{ bearer::{BearerDid, BearerDidError}, @@ -8,17 +6,12 @@ use dids::{ resolver::{ResolutionError, Resolver}, }; use serde::{Deserialize, Serialize}; +use serde_json::Error as SerdeJsonError; #[derive(thiserror::Error, Debug, Clone, PartialEq)] pub enum JwsError { #[error(transparent)] BearerDidError(#[from] BearerDidError), - #[error("serialization error {0}")] - SerializationError(String), - #[error("deserialization error {0}")] - DeserializationError(String), - #[error("decoding error {0}")] - DecodingError(String), #[error("incorrect number of parts 3 expected {0}")] IncorrectPartsLength(String), #[error(transparent)] @@ -29,16 +22,18 @@ pub enum JwsError { AlgorithmNotFound(String), #[error(transparent)] CryptoError(#[from] CryptoError), - #[error("deserialization error {0}")] + #[error("serde json error {0}")] + SerdeJsonError(String), + #[error(transparent)] + DecodeError(#[from] DecodeError), + #[error("Malformed Header: {0}")] MalformedHeader(String), } -pub fn splice_parts(compact_jws: &str) -> Result, JwsError> { - let parts: Vec = compact_jws.split('.').map(|x| x.to_string()).collect(); - if parts.len() != 3 { - return Err(JwsError::IncorrectPartsLength(compact_jws.to_string())); +impl From for JwsError { + fn from(err: SerdeJsonError) -> Self { + JwsError::SerdeJsonError(err.to_string()) } - Ok(parts) } #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -48,210 +43,163 @@ pub struct JwsHeader { pub typ: String, } -impl JwsHeader { - pub fn new(alg: String, kid: String, typ: String) -> Self { - Self { alg, kid, typ } - } - - pub fn new_from_encoded(encoded_jws_header: &str) -> Result { - let decoded_bytes = general_purpose::URL_SAFE_NO_PAD - .decode(encoded_jws_header) - .map_err(|e| JwsError::DecodingError(e.to_string()))?; - let jws_header = serde_json::from_slice(&decoded_bytes) - .map_err(|e| JwsError::DeserializationError(e.to_string()))?; - Ok(jws_header) - } - - pub fn from_bearer_did( - bearer_did: &BearerDid, - key_selector: &KeySelector, - typ: &str, - ) -> Result { - let verification_method = bearer_did.document.get_verification_method(key_selector)?; - let kid = verification_method.id; - let alg = match verification_method.public_key_jwk.crv.as_str() { - "secp256k1" => "ES256K".to_string(), - "Ed25519" => "EdDSA".to_string(), - _ => return Err(JwsError::AlgorithmNotFound(kid)), - }; - Ok(Self { - alg, - kid, - typ: typ.to_string(), - }) - } +pub struct JwsDecoded { + pub header: JwsHeader, + pub payload: Vec, + pub signature: String, + pub parts: Vec, +} - pub fn encode(&self) -> Result { - let json_str = serde_json::to_string(&self) - .map_err(|e| JwsError::SerializationError(e.to_string()))?; - let encoded_str = general_purpose::URL_SAFE_NO_PAD.encode(json_str.as_bytes()); - Ok(encoded_str) - } +pub struct CompactJws; - pub fn sign_compact_jws( - &self, +impl CompactJws { + pub fn sign( bearer_did: &BearerDid, key_selector: &KeySelector, - encoded_payload: &str, + header: &JwsHeader, + payload: &[u8], // JSON string as a byte array, TODO add a doc comment for this ) -> Result { - let encoded_header = self.encode()?; - let compact_jws = - sign_compact_jws(bearer_did, key_selector, &encoded_header, encoded_payload)?; + let header_json_string = serde_json::to_string(header)?; + let encoded_header = general_purpose::URL_SAFE_NO_PAD.encode(header_json_string.as_bytes()); + let encoded_payload = general_purpose::URL_SAFE_NO_PAD.encode(payload); + + let to_sign = format!("{}.{}", encoded_header, encoded_payload); + let signature = bearer_did.sign(key_selector, &to_sign.into_bytes())?; + let encoded_signature = general_purpose::URL_SAFE_NO_PAD.encode(signature); + let compact_jws = format!( + "{}.{}.{}", + encoded_header, encoded_payload, encoded_signature + ); Ok(compact_jws) } -} -pub fn sign_compact_jws( - bearer_did: &BearerDid, - key_selector: &KeySelector, - encoded_header: &str, - encoded_payload: &str, -) -> Result { - let to_sign = format!("{}.{}", encoded_header, encoded_payload); - let signature = bearer_did.sign(key_selector, &to_sign.into_bytes())?; - let encoded_signature = general_purpose::URL_SAFE_NO_PAD.encode(signature); - let compact_jws = format!( - "{}.{}.{}", - encoded_header, encoded_payload, encoded_signature - ); - Ok(compact_jws) -} + pub fn decode(compact_jws: &str) -> Result { + let parts: Vec = compact_jws.split('.').map(|x| x.to_string()).collect(); + if parts.len() != 3 { + return Err(JwsError::IncorrectPartsLength(compact_jws.to_string())); + } -pub async fn verify_compact_jws(compact_jws: &str) -> Result<(), JwsError> { - let parts = splice_parts(compact_jws)?; - let jws_header = JwsHeader::new_from_encoded(&parts[0])?; - let key_id = jws_header.kid.clone(); - let did_uri = KeyIdFragment(key_id.clone()).splice_uri(); - let resolution_result = Resolver::resolve_uri(&did_uri).await; - if let Some(err) = resolution_result.did_resolution_metadata.error { - return Err(JwsError::ResolutionError(err)); + let decoded_header = general_purpose::URL_SAFE_NO_PAD.decode(&parts[0])?; + let header = serde_json::from_slice::(&decoded_header)?; + + let decoded_payload = general_purpose::URL_SAFE_NO_PAD.decode(&parts[1])?; + + Ok(JwsDecoded { + header, + payload: decoded_payload, + signature: parts[2].to_string(), + parts, + }) } - let verification_method = match resolution_result.did_document { - Some(document) => document.get_verification_method(&KeySelector::KeyId { key_id }), - None => { - return Err(JwsError::DocumentError( - DocumentError::VerificationMethodNotFound, - )) + + pub async fn verify(compact_jws: &str) -> Result { + let jws_decoded = CompactJws::decode(compact_jws)?; + + // Validate header fields + if jws_decoded.header.alg.is_empty() { + return Err(JwsError::MalformedHeader( + "alg field is required".to_string(), + )); + } + + if jws_decoded.header.kid.is_empty() { + return Err(JwsError::MalformedHeader( + "kid field is required for verification processing".to_string(), + )); + } + + let key_id = jws_decoded.header.kid.clone(); + let did_uri = KeyIdFragment(key_id.clone()).splice_uri(); + let resolution_result = Resolver::resolve_uri(&did_uri).await; + if let Some(err) = resolution_result.did_resolution_metadata.error { + return Err(JwsError::ResolutionError(err)); } - }?; - let public_key = verification_method.public_key_jwk.clone(); - let to_verify = format!("{}.{}", parts[0], parts[1]); - let alg = jws_header.alg.clone(); - let decoded_signature = general_purpose::URL_SAFE_NO_PAD - .decode(&parts[2]) - .map_err(|e| JwsError::DecodingError(e.to_string()))?; - match alg.as_str() { - "EdDSA" => Ed25519::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), - "ES256K" => Secp256k1::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), - _ => return Err(JwsError::AlgorithmNotFound(alg)), - }?; - Ok(()) + let verification_method = match resolution_result.did_document { + Some(document) => document.get_verification_method(&KeySelector::KeyId { key_id }), + None => { + return Err(JwsError::DocumentError( + DocumentError::VerificationMethodNotFound, + )) + } + }?; + let public_key = verification_method.public_key_jwk.clone(); + let to_verify = format!("{}.{}", jws_decoded.parts[0], jws_decoded.parts[1]); + let alg = jws_decoded.header.alg.clone(); + let decoded_signature = general_purpose::URL_SAFE_NO_PAD.decode(&jws_decoded.parts[2])?; + match alg.as_str() { + "EdDSA" => Ed25519::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), + "ES256K" => Secp256k1::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), + _ => return Err(JwsError::AlgorithmNotFound(alg)), + }?; + Ok(jws_decoded) + } } #[cfg(test)] -mod test { +mod tests { use super::*; use crypto::Curve; - use dids::{ - document::VerificationMethodType, - method::{ - jwk::{DidJwk, DidJwkCreateOptions}, - Create, - }, + use dids::method::{ + jwk::{DidJwk, DidJwkCreateOptions}, + Create, }; use keys::key_manager::local_key_manager::LocalKeyManager; + use serde_json::json; use std::sync::Arc; - #[test] - fn test_from_encoded() { - let compact_jws = "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNrRnZTek5EYkdGM1UwWlhTRkJ1T0VGMk5rbHFiR3BDVTNkc1pERnZhMEp5WDI0elkwMWFkVWQ0WXlKOSMwIiwidHlwIjoiSldUIn0.eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ.XhJrLvSoQL3N8AOM3OtLBq45K2IFJUiaAwWBPTscwkEKH3I1wExs1-AhTaCvyGwDpCGDmm7T21pKnNwPsoCTCw"; - let parts = splice_parts(compact_jws).unwrap(); - let jws_header = - JwsHeader::new_from_encoded(&parts[0]).expect("failed to instantiate from compact jws"); - assert_eq!(jws_header.alg, "EdDSA".to_string()); - assert_eq!(jws_header.kid, "did:jwk:eyJhbGciOiJFZERTQSIsImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoickFvSzNDbGF3U0ZXSFBuOEF2NklqbGpCU3dsZDFva0JyX24zY01adUd4YyJ9#0".to_string()); - assert_eq!(jws_header.typ, "JWT".to_string()); - } - - #[test] - fn test_from_bearer_did() { - let key_manager = Arc::new(LocalKeyManager::new_in_memory()); - let options = DidJwkCreateOptions { - curve: Curve::Ed25519, - }; - let bearer_did = DidJwk::create(key_manager, options).unwrap(); - let key_selector = KeySelector::MethodType { - verification_method_type: VerificationMethodType::VerificationMethod, - }; - let jws_header = JwsHeader::from_bearer_did(&bearer_did, &key_selector, "JWT") - .expect("failed to instantiate JwsHeader from bearer did"); - - assert_eq!(jws_header.alg, "EdDSA".to_string()); - assert_eq!( - jws_header.kid, - bearer_did - .document - .get_verification_method(&key_selector) - .unwrap() - .id - ); - assert_eq!(jws_header.typ, "JWT".to_string()); - } - - #[test] - fn test_encode() { - let jws_header = JwsHeader::new("EdDSA".to_string(), "did:jwk:eyJhbGciOiJFZERTQSIsImNydiI6IkVkMjU1MTkiLCJrdHkiOiJPS1AiLCJ4IjoickFvSzNDbGF3U0ZXSFBuOEF2NklqbGpCU3dsZDFva0JyX24zY01adUd4YyJ9#0".to_string(), "JWT".to_string()); - let encoded = jws_header.encode().expect("failed to encode jws header"); - assert!(encoded.len() > 0); - - let new_jws_header = JwsHeader::new_from_encoded(&encoded).unwrap(); - assert_eq!(jws_header, new_jws_header); - } - - #[test] - fn test_sign_compact_jws_self() { - let key_manager = Arc::new(LocalKeyManager::new_in_memory()); - let options = DidJwkCreateOptions { - curve: Curve::Ed25519, - }; - let bearer_did = DidJwk::create(key_manager, options).unwrap(); - let key_selector = KeySelector::MethodType { - verification_method_type: VerificationMethodType::VerificationMethod, + #[tokio::test] + async fn test_jws_sign_and_verify() { + let key_manager = LocalKeyManager::new_in_memory(); + let bearer_did = DidJwk::create( + Arc::new(key_manager), + DidJwkCreateOptions { + curve: Curve::Ed25519, + }, + ) + .expect("failed to create bearer did"); + + let key_id = bearer_did.document.verification_method[0].id.clone(); + + let header = JwsHeader { + alg: "EdDSA".to_string(), + kid: key_id.clone(), + typ: "JWT".to_string(), }; - let jws_header = JwsHeader::from_bearer_did(&bearer_did, &key_selector, "JWT") - .expect("failed to instantiate JwsHeader from bearer did"); - - let encoded_payload = "eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ"; - let signed = jws_header - .sign_compact_jws(&bearer_did, &key_selector, encoded_payload) - .unwrap(); - assert!(signed.len() > 0); + let payload = json!({ + "sub": "1234567890", + "name": "John Doe", + "iat": 1516239022 + }) + .to_string() + .into_bytes(); + + let compact_jws = CompactJws::sign( + &bearer_did, + &KeySelector::KeyId { + key_id: key_id.clone(), + }, + &header, + &payload, + ) + .unwrap(); + + let verified_jws = CompactJws::verify(&compact_jws).await.unwrap(); + + assert_eq!(verified_jws.header, header); + assert_eq!(verified_jws.payload, payload); } #[test] - fn test_sign_compact_jws() { - let key_manager = Arc::new(LocalKeyManager::new_in_memory()); - let options = DidJwkCreateOptions { - curve: Curve::Ed25519, - }; - let bearer_did = DidJwk::create(key_manager, options).unwrap(); - let key_selector = KeySelector::MethodType { - verification_method_type: VerificationMethodType::VerificationMethod, - }; - let jws_header = JwsHeader::from_bearer_did(&bearer_did, &key_selector, "JWT") - .expect("failed to instantiate JwsHeader from bearer did"); - - let encoded_header = jws_header.encode().unwrap(); - let encoded_payload = "eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ"; - let signed = - sign_compact_jws(&bearer_did, &key_selector, &encoded_header, encoded_payload).unwrap(); - assert!(signed.len() > 0); + fn test_jws_decode_error() { + let invalid_jws = "invalid.jws"; + let result = CompactJws::decode(invalid_jws); + assert!(matches!(result, Err(JwsError::IncorrectPartsLength(_)))); } - #[tokio::test] - async fn test_verify() { - let compact_jws = "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaWNXWnZObmN5VVRWaFExcDJZemxZUTFBd2FXWnRZWFJvYkdKSGRHVkxja1V5Y2xWR1lVSldkRVJqU1NKOSMwIiwidHlwIjoiSldUIn0.eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ.IHupSrKGXg-5q-769RAfomDre_gSv2P4_i9JjynRUHybSwTyvCRz7U-THx2KVsp4NCtyaWRXQz4f0GyZCSYxDA"; - let result = verify_compact_jws(compact_jws).await; - assert!(result.is_ok()); - } + // TODO https://github.com/TBD54566975/web5-rs/issues/166 + // - not base64 encoded signature + // - base64 encoded signature but not valid cryptographic signature + // - not supported algorithm + // - did doc doesn't resolve + // - did doc missing vm } diff --git a/crates/jws/src/v2.rs b/crates/jws/src/v2.rs deleted file mode 100644 index ac9b5896..00000000 --- a/crates/jws/src/v2.rs +++ /dev/null @@ -1,205 +0,0 @@ -use base64::{engine::general_purpose, DecodeError, Engine as _}; -use crypto::{ed25519::Ed25519, secp256k1::Secp256k1, CryptoError, CurveOperations}; -use dids::{ - bearer::{BearerDid, BearerDidError}, - document::{DocumentError, KeyIdFragment, KeySelector}, - resolver::{ResolutionError, Resolver}, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Error as SerdeJsonError; - -#[derive(thiserror::Error, Debug, Clone, PartialEq)] -pub enum JwsError { - #[error(transparent)] - BearerDidError(#[from] BearerDidError), - #[error("incorrect number of parts 3 expected {0}")] - IncorrectPartsLength(String), - #[error(transparent)] - DocumentError(#[from] DocumentError), - #[error(transparent)] - ResolutionError(#[from] ResolutionError), - #[error("algorithm not found {0}")] - AlgorithmNotFound(String), - #[error(transparent)] - CryptoError(#[from] CryptoError), - #[error("serde json error {0}")] - SerdeJsonError(String), - #[error(transparent)] - DecodeError(#[from] DecodeError), - #[error("Malformed Header: {0}")] - MalformedHeader(String), -} - -impl From for JwsError { - fn from(err: SerdeJsonError) -> Self { - JwsError::SerdeJsonError(err.to_string()) - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct JwsHeader { - pub alg: String, - pub kid: String, - pub typ: String, -} - -pub struct JwsDecoded { - pub header: JwsHeader, - pub payload: Vec, - pub signature: String, - pub parts: Vec, -} - -pub struct CompactJws; - -impl CompactJws { - pub fn sign( - bearer_did: &BearerDid, - key_selector: &KeySelector, - header: &JwsHeader, - payload: &[u8], // JSON string as a byte array, TODO add a doc comment for this - ) -> Result { - let header_json_string = serde_json::to_string(header)?; - let encoded_header = general_purpose::URL_SAFE_NO_PAD.encode(header_json_string.as_bytes()); - let encoded_payload = general_purpose::URL_SAFE_NO_PAD.encode(payload); - - let to_sign = format!("{}.{}", encoded_header, encoded_payload); - let signature = bearer_did.sign(key_selector, &to_sign.into_bytes())?; - let encoded_signature = general_purpose::URL_SAFE_NO_PAD.encode(signature); - let compact_jws = format!( - "{}.{}.{}", - encoded_header, encoded_payload, encoded_signature - ); - Ok(compact_jws) - } - - pub fn decode(compact_jws: &str) -> Result { - let parts: Vec = compact_jws.split('.').map(|x| x.to_string()).collect(); - if parts.len() != 3 { - return Err(JwsError::IncorrectPartsLength(compact_jws.to_string())); - } - - let decoded_header = general_purpose::URL_SAFE_NO_PAD.decode(&parts[0])?; - let header = serde_json::from_slice::(&decoded_header)?; - - let decoded_payload = general_purpose::URL_SAFE_NO_PAD.decode(&parts[1])?; - - Ok(JwsDecoded { - header, - payload: decoded_payload, - signature: parts[2].to_string(), - parts, - }) - } - - pub async fn verify(compact_jws: &str) -> Result { - let jws_decoded = CompactJws::decode(compact_jws)?; - - // Validate header fields - if jws_decoded.header.alg.is_empty() { - return Err(JwsError::MalformedHeader( - "alg field is required".to_string(), - )); - } - - if jws_decoded.header.kid.is_empty() { - return Err(JwsError::MalformedHeader( - "kid field is required for verification processing".to_string(), - )); - } - - let key_id = jws_decoded.header.kid.clone(); - let did_uri = KeyIdFragment(key_id.clone()).splice_uri(); - let resolution_result = Resolver::resolve_uri(&did_uri).await; - if let Some(err) = resolution_result.did_resolution_metadata.error { - return Err(JwsError::ResolutionError(err)); - } - let verification_method = match resolution_result.did_document { - Some(document) => document.get_verification_method(&KeySelector::KeyId { key_id }), - None => { - return Err(JwsError::DocumentError( - DocumentError::VerificationMethodNotFound, - )) - } - }?; - let public_key = verification_method.public_key_jwk.clone(); - let to_verify = format!("{}.{}", jws_decoded.parts[0], jws_decoded.parts[1]); - let alg = jws_decoded.header.alg.clone(); - let decoded_signature = general_purpose::URL_SAFE_NO_PAD.decode(&jws_decoded.parts[2])?; - match alg.as_str() { - "EdDSA" => Ed25519::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), - "ES256K" => Secp256k1::verify(&public_key, &to_verify.into_bytes(), &decoded_signature), - _ => return Err(JwsError::AlgorithmNotFound(alg)), - }?; - Ok(jws_decoded) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crypto::Curve; - use dids::method::{ - jwk::{DidJwk, DidJwkCreateOptions}, - Create, - }; - use keys::key_manager::local_key_manager::LocalKeyManager; - use serde_json::json; - use std::sync::Arc; - - #[tokio::test] - async fn test_jws_sign_and_verify() { - let key_manager = LocalKeyManager::new_in_memory(); - let bearer_did = DidJwk::create( - Arc::new(key_manager), - DidJwkCreateOptions { - curve: Curve::Ed25519, - }, - ) - .expect("failed to create bearer did"); - - let key_id = bearer_did.document.verification_method[0].id.clone(); - - let header = JwsHeader { - alg: "EdDSA".to_string(), - kid: key_id.clone(), - typ: "JWT".to_string(), - }; - let payload = json!({ - "sub": "1234567890", - "name": "John Doe", - "iat": 1516239022 - }) - .to_string() - .into_bytes(); - - let compact_jws = CompactJws::sign( - &bearer_did, - &KeySelector::KeyId { - key_id: key_id.clone(), - }, - &header, - &payload, - ) - .unwrap(); - - let verified_jws = CompactJws::verify(&compact_jws).await.unwrap(); - - assert_eq!(verified_jws.header, header); - assert_eq!(verified_jws.payload, payload); - } - - #[test] - fn test_jws_decode_error() { - let invalid_jws = "invalid.jws"; - let result = CompactJws::decode(invalid_jws); - assert!(matches!(result, Err(JwsError::IncorrectPartsLength(_)))); - } - - // TODO https://github.com/TBD54566975/web5-rs/issues/166 - // - not base64 encoded signature - // - base64 encoded signature but not valid cryptographic signature - // - not supported algorithm - // - did doc doesn't resolve - // - did doc missing vm -} diff --git a/crates/jwt/src/jwe.rs b/crates/jwt/src/jwe.rs index 36674ce2..3bc05173 100644 --- a/crates/jwt/src/jwe.rs +++ b/crates/jwt/src/jwe.rs @@ -1,6 +1,6 @@ -use crate::lib_v2::{Claims, JwtError}; +use crate::{Claims, JwtError}; use dids::{bearer::BearerDid, document::KeySelector}; -use jws::v2::JwsHeader; +use jws::JwsHeader; // A JWT can be implemented as either a JWS or JWE, this module is the implementation of a JWT as a JWE diff --git a/crates/jwt/src/jws.rs b/crates/jwt/src/jws.rs index edf90243..6008a8ae 100644 --- a/crates/jwt/src/jws.rs +++ b/crates/jwt/src/jws.rs @@ -1,6 +1,6 @@ -use crate::lib_v2::{Claims, JwtError}; +use crate::{Claims, JwtError}; +use ::jws::{CompactJws, JwsHeader}; use dids::{bearer::BearerDid, document::KeySelector}; -use jws::v2::{CompactJws, JwsHeader}; // A JWT can be implemented as either a JWS or JWE, this module is the implementation of a JWT as a JWS @@ -70,7 +70,7 @@ impl Jwt { #[cfg(test)] mod tests { use super::*; - use crate::lib_v2::RegisteredClaims; + use crate::RegisteredClaims; use crypto::Curve; use dids::{ document::KeySelector, diff --git a/crates/jwt/src/lib.rs b/crates/jwt/src/lib.rs index d15225b7..e469f848 100644 --- a/crates/jwt/src/lib.rs +++ b/crates/jwt/src/lib.rs @@ -1,24 +1,31 @@ pub mod jwe; pub mod jws; -pub mod lib_v2; -use ::jws::{sign_compact_jws, verify_compact_jws, JwsError, JwsHeader}; -use base64::{engine::general_purpose, Engine as _}; -use dids::{bearer::BearerDid, document::KeySelector}; -use serde::{Deserialize, Serialize}; +use ::jws::JwsError; +use dids::document::DocumentError; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Error as SerdeJsonError; #[derive(thiserror::Error, Debug, Clone, PartialEq)] pub enum JwtError { #[error(transparent)] JwsError(#[from] JwsError), - #[error("serialization error {0}")] - SerializationError(String), - #[error("deserialization error {0}")] - DeserializationError(String), + #[error(transparent)] + DocumentError(#[from] DocumentError), + #[error("serde json error {0}")] + SerdeJsonError(String), +} + +impl From for JwtError { + fn from(err: SerdeJsonError) -> Self { + JwtError::SerdeJsonError(err.to_string()) + } } +pub trait Claims: Serialize + DeserializeOwned {} + #[derive(Serialize, Deserialize, Debug, Default)] -pub struct Claims { +pub struct RegisteredClaims { #[serde(rename = "iss", skip_serializing_if = "Option::is_none")] pub issuer: Option, #[serde(rename = "sub", skip_serializing_if = "Option::is_none")] @@ -35,124 +42,4 @@ pub struct Claims { pub jti: Option, } -impl Claims { - pub fn new( - issuer: Option, - subject: Option, - audience: Option, - expiration: Option, - not_before: Option, - issued_at: Option, - jti: Option, - ) -> Self { - Self { - issuer, - subject, - audience, - expiration, - not_before, - issued_at, - jti, - } - } - - pub fn encode(&self) -> Result { - let json_str = serde_json::to_string(&self) - .map_err(|e| JwtError::SerializationError(e.to_string()))?; - let encoded_str = general_purpose::URL_SAFE_NO_PAD.encode(json_str.as_bytes()); - Ok(encoded_str) - } - - pub fn sign( - &self, - bearer_did: &BearerDid, - key_selector: &KeySelector, - ) -> Result { - let encoded_payload = self.encode()?; - let jws_header = JwsHeader::from_bearer_did(bearer_did, key_selector, "JWT")?; - let compact_jws = - jws_header.sign_compact_jws(bearer_did, key_selector, &encoded_payload)?; - Ok(compact_jws) - } -} - -pub fn sign_jwt( - bearer_did: &BearerDid, - key_selector: &KeySelector, - encoded_header: &str, - encoded_payload: &str, -) -> Result { - let compact_jws = sign_compact_jws(bearer_did, key_selector, encoded_header, encoded_payload)?; - Ok(compact_jws) -} - -pub async fn verify_jwt(jwt: &str) -> Result<(), JwtError> { - verify_compact_jws(jwt).await?; - Ok(()) -} - -#[cfg(test)] -mod test { - use ::jws::splice_parts; - use crypto::Curve; - use dids::{ - document::VerificationMethodType, - method::{ - jwk::{DidJwk, DidJwkCreateOptions}, - Create, - }, - }; - use keys::key_manager::local_key_manager::LocalKeyManager; - - use super::*; - use std::sync::Arc; - - #[test] - fn test_encode() { - let claims = Claims { - issuer: Some("did:example:123".to_string()), - ..Default::default() - }; - let encoded = claims.encode().expect("failed to encode"); - assert_ne!(0, encoded.len()); - } - - #[test] - fn test_sign() { - let key_manager = Arc::new(LocalKeyManager::new_in_memory()); - let options = DidJwkCreateOptions { - curve: Curve::Ed25519, - }; - let bearer_did = DidJwk::create(key_manager, options).unwrap(); - let key_selector = KeySelector::MethodType { - verification_method_type: VerificationMethodType::VerificationMethod, - }; - - let claims = Claims { - issuer: Some("did:example:123".to_string()), - ..Default::default() - }; - let signed = claims - .sign(&bearer_did, &key_selector) - .expect("failed to sign jwt"); - assert!(signed.len() > 0); - - let encoded_header = splice_parts(&signed).unwrap()[0].clone(); - let encoded_payload = claims.encode().expect("failed to encode"); - let signed2 = sign_jwt( - &bearer_did, - &key_selector, - &encoded_header, - &encoded_payload, - ) - .expect("failed to sign jwt"); - assert!(signed2.len() > 0); - } - - #[tokio::test] - async fn test_verify() { - let jwt = "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJkSGtpT2lKUFMxQWlMQ0o0SWpvaVpIaHlUemhwWjJOaFVuQlRaRlZ0Ylc5QlRXaG1TRE5uVmtOV1kxTkpaWEp6WjBaYU1YUnFYMTlOVlNKOSMwIiwidHlwIjoiSldUIn0.eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ.aGu5KNVmNV1o35cyksJ5A1uCbDp5Z1moROPGwnsxNfTKC9aPbmAJVICaE9dB2lU79vIuVTgVFrs_octfB_wvAg"; - let result = verify_jwt(jwt).await; - assert!(result.is_ok()); - } -} +impl Claims for RegisteredClaims {} diff --git a/crates/jwt/src/lib_v2.rs b/crates/jwt/src/lib_v2.rs deleted file mode 100644 index e6928449..00000000 --- a/crates/jwt/src/lib_v2.rs +++ /dev/null @@ -1,42 +0,0 @@ -use dids::document::DocumentError; -use jws::v2::JwsError; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Error as SerdeJsonError; - -#[derive(thiserror::Error, Debug, Clone, PartialEq)] -pub enum JwtError { - #[error(transparent)] - JwsError(#[from] JwsError), - #[error(transparent)] - DocumentError(#[from] DocumentError), - #[error("serde json error {0}")] - SerdeJsonError(String), -} - -impl From for JwtError { - fn from(err: SerdeJsonError) -> Self { - JwtError::SerdeJsonError(err.to_string()) - } -} - -pub trait Claims: Serialize + DeserializeOwned {} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct RegisteredClaims { - #[serde(rename = "iss", skip_serializing_if = "Option::is_none")] - pub issuer: Option, - #[serde(rename = "sub", skip_serializing_if = "Option::is_none")] - pub subject: Option, - #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] - pub audience: Option, - #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] - pub expiration: Option, - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - pub not_before: Option, - #[serde(rename = "iat", skip_serializing_if = "Option::is_none")] - pub issued_at: Option, - #[serde(rename = "jti", skip_serializing_if = "Option::is_none")] - pub jti: Option, -} - -impl Claims for RegisteredClaims {}