Skip to content

Commit

Permalink
feat: add support for using multiple DID methods (#70)
Browse files Browse the repository at this point in the history
* feat: add support for multiple DID methods for `Subject`

* fix: remove `Subjects` type

* feat: add `default_subject_syntax_type`

* feat: add `default_subject_syntax_type` getter for `RelyingPartyManager` and `ProviderManager`
  • Loading branch information
nanderstabel authored Apr 20, 2024
1 parent 9b3b324 commit a932af7
Show file tree
Hide file tree
Showing 31 changed files with 266 additions and 306 deletions.
4 changes: 2 additions & 2 deletions oid4vc-core/src/authentication/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::sync::Arc;
pub trait Sign: Send + Sync {
// TODO: add this?
// fn jwt_alg_name() -> &'static str;
fn key_id(&self) -> Option<String>;
fn sign(&self, message: &str) -> Result<Vec<u8>>;
fn key_id(&self, subject_syntax_type: &str) -> Option<String>;
fn sign(&self, message: &str, subject_syntax_type: &str) -> Result<Vec<u8>>;
fn external_signer(&self) -> Option<Arc<dyn ExternalSign>>;
}

Expand Down
32 changes: 3 additions & 29 deletions oid4vc-core/src/authentication/subject.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
use crate::{Collection, Sign, SubjectSyntaxType, Verify};
use crate::{Sign, Verify};
use anyhow::Result;
use std::{str::FromStr, sync::Arc};
use std::sync::Arc;

pub type SigningSubject = Arc<dyn Subject>;

// TODO: Use a URI of some sort.
/// This [`Subject`] trait is used to sign and verify JWTs.
pub trait Subject: Sign + Verify + Send + Sync {
fn identifier(&self) -> Result<String>;
fn type_(&self) -> Result<SubjectSyntaxType> {
SubjectSyntaxType::from_str(&self.identifier()?)
}
}

pub type Subjects = Collection<dyn Subject>;

impl Subjects {
pub fn get_subject(&self, subject_syntax_type: SubjectSyntaxType) -> Option<Arc<dyn Subject>> {
self.iter()
.find(|&subject| *subject.0 == subject_syntax_type)
.map(|subject| subject.1.clone())
}
}

impl<const N: usize> TryFrom<[Arc<dyn Subject>; N]> for Subjects {
type Error = anyhow::Error;

fn try_from(subjects: [Arc<dyn Subject>; N]) -> Result<Self> {
Ok(Self::from(
subjects
.iter()
.map(|subject| subject.type_().map(|subject_type| (subject_type, subject.clone())))
.collect::<Result<Vec<_>>>()?,
))
}
fn identifier(&self, subject_syntax_type: &str) -> Result<String>;
}
28 changes: 9 additions & 19 deletions oid4vc-core/src/authentication/validator.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
use crate::{Collection, Subject, Subjects, Verify};
use crate::{jwt, Subject, Verify};
use anyhow::Result;
use serde::de::DeserializeOwned;
use std::sync::Arc;

pub type Validators = Collection<Validator>;

impl From<&Subjects> for Validators {
fn from(subjects: &Subjects) -> Self {
Self::from(
subjects
.iter()
.map(|(subject_syntax_type, subject)| {
(
subject_syntax_type.clone(),
Arc::new(Validator::Subject(subject.clone())),
)
})
.collect::<Vec<_>>(),
)
}
}

pub enum Validator {
Subject(Arc<dyn Subject>),
Verifier(Arc<dyn Verify>),
Expand All @@ -32,4 +15,11 @@ impl Validator {
Validator::Verifier(verifier) => verifier.public_key(kid).await,
}
}

pub async fn decode<T: DeserializeOwned>(&self, jwt: String) -> Result<T> {
let (kid, algorithm) = jwt::extract_header(&jwt)?;

let public_key = self.public_key(&kid).await?;
jwt::decode(&jwt, public_key, algorithm)
}
}
37 changes: 0 additions & 37 deletions oid4vc-core/src/collection.rs

This file was deleted.

29 changes: 0 additions & 29 deletions oid4vc-core/src/decoder.rs

This file was deleted.

10 changes: 6 additions & 4 deletions oid4vc-core/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,20 @@ where
Ok(jsonwebtoken::decode::<T>(jwt, &key, &validation)?.claims)
}

pub fn encode<C, S>(signer: Arc<S>, header: Header, claims: C) -> Result<String>
pub fn encode<C, S>(signer: Arc<S>, header: Header, claims: C, subject_syntax_type: &str) -> Result<String>
where
C: Serialize,
S: Sign + ?Sized,
{
let kid = signer.key_id().ok_or(anyhow!("No key identifier found."))?;
let kid = signer
.key_id(subject_syntax_type)
.ok_or(anyhow!("No key identifier found."))?;

let jwt = JsonWebToken::new(header, claims).kid(kid);

let message = [base64_url_encode(&jwt.header)?, base64_url_encode(&jwt.payload)?].join(".");

let proof_value = signer.sign(&message)?;
let proof_value = signer.sign(&message, subject_syntax_type)?;
let signature = base64_url::encode(proof_value.as_slice());
let message = [message, signature].join(".");
Ok(message)
Expand Down Expand Up @@ -95,7 +97,7 @@ mod tests {
"nonce": "nonce",
});
let subject = TestSubject::new("did:test:123".to_string(), "key_id".to_string()).unwrap();
let encoded = encode(Arc::new(subject), Header::new(Algorithm::EdDSA), claims).unwrap();
let encoded = encode(Arc::new(subject), Header::new(Algorithm::EdDSA), claims, "did:test").unwrap();

let verifier = MockVerifier::new();
let (kid, algorithm) = extract_header(&encoded).unwrap();
Expand Down
11 changes: 1 addition & 10 deletions oid4vc-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,13 @@ pub mod authentication;
pub mod authorization_request;
pub mod authorization_response;
pub mod client_metadata;
pub mod collection;
pub mod decoder;
pub mod jwt;
pub mod openid4vc_extension;
pub mod rfc7519_claims;
pub mod scope;
pub mod subject_syntax_type;

pub use authentication::{
sign::Sign,
subject::{Subject, Subjects},
validator::{Validator, Validators},
verify::Verify,
};
pub use collection::Collection;
pub use decoder::Decoder;
pub use authentication::{sign::Sign, subject::Subject, validator::Validator, verify::Verify};
use rand::{distributions::Alphanumeric, Rng};
pub use rfc7519_claims::RFC7519Claims;
use serde::Serialize;
Expand Down
5 changes: 3 additions & 2 deletions oid4vc-core/src/openid4vc_extension.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{authorization_response::AuthorizationResponse, Decoder, Subject};
use crate::{authorization_response::AuthorizationResponse, Subject, SubjectSyntaxType, Validator};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{future::Future, sync::Arc};

Expand Down Expand Up @@ -28,6 +28,7 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S
_client_id: &str,
_extension_parameters: &<Self::RequestHandle as RequestHandle>::Parameters,
_user_input: &<Self::ResponseHandle as ResponseHandle>::Input,
_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
) -> anyhow::Result<Vec<String>> {
// Will be overwritten by the extension.
Err(anyhow::anyhow!("Not implemented."))
Expand All @@ -44,7 +45,7 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S
}

fn decode_authorization_response(
_decoder: Decoder,
_validator: Validator,
_authorization_response: &AuthorizationResponse<Self>,
) -> impl Future<Output = anyhow::Result<<Self::ResponseHandle as ResponseHandle>::ResponseItem>> + Send {
// Will be overwritten by the extension.
Expand Down
17 changes: 17 additions & 0 deletions oid4vc-core/src/subject_syntax_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ impl FromStr for SubjectSyntaxType {
}
}

impl Display for SubjectSyntaxType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SubjectSyntaxType::JwkThumbprint => write!(f, "urn:ietf:params:oauth:jwk-thumbprint"),
SubjectSyntaxType::Did(did_method) => write!(f, "{}", did_method),
}
}
}

impl TryFrom<&str> for SubjectSyntaxType {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
SubjectSyntaxType::from_str(value)
}
}

impl From<DidMethod> for SubjectSyntaxType {
fn from(did_method: DidMethod) -> Self {
SubjectSyntaxType::Did(did_method)
Expand Down
6 changes: 3 additions & 3 deletions oid4vc-core/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ impl TestSubject {
}

impl Sign for TestSubject {
fn key_id(&self) -> Option<String> {
fn key_id(&self, _subject_syntax_type: &str) -> Option<String> {
Some(self.key_id.clone())
}

fn sign(&self, message: &str) -> Result<Vec<u8>> {
fn sign(&self, message: &str, _subject_syntax_type: &str) -> Result<Vec<u8>> {
let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes());
Ok(signature.to_bytes().to_vec())
}
Expand All @@ -53,7 +53,7 @@ impl Verify for TestSubject {
}

impl Subject for TestSubject {
fn identifier(&self) -> Result<String> {
fn identifier(&self, _subject_syntax_type: &str) -> Result<String> {
Ok(self.did.to_string())
}
}
Expand Down
17 changes: 5 additions & 12 deletions oid4vc-manager/src/managers/credential_issuer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::storage::Storage;
use anyhow::Result;
use oid4vc_core::{Subject, Subjects};
use oid4vc_core::Subject;
use oid4vci::{
credential_format_profiles::CredentialFormatCollection,
credential_issuer::{
Expand All @@ -15,26 +15,19 @@ use std::{net::TcpListener, sync::Arc};
#[derive(Clone)]
pub struct CredentialIssuerManager<S: Storage<CFC>, CFC: CredentialFormatCollection> {
pub credential_issuer: CredentialIssuer<CFC>,
pub subjects: Arc<Subjects>,
pub subject: Arc<dyn Subject>,
pub storage: S,
pub listener: Arc<TcpListener>,
}

impl<S: Storage<CFC>, CFC: CredentialFormatCollection> CredentialIssuerManager<S, CFC> {
pub fn new<const N: usize>(
listener: Option<TcpListener>,
storage: S,
subjects: [Arc<dyn Subject>; N],
) -> Result<Self> {
pub fn new(listener: Option<TcpListener>, storage: S, subject: Arc<dyn Subject>) -> Result<Self> {
// `TcpListener::bind("127.0.0.1:0")` will bind to a random port.
let listener = listener.unwrap_or_else(|| TcpListener::bind("127.0.0.1:0").unwrap());
let issuer_url: Url = format!("http://{:?}", listener.local_addr()?).parse()?;
Ok(Self {
credential_issuer: CredentialIssuer {
subject: subjects
.first()
.ok_or_else(|| anyhow::anyhow!("No subjects found."))?
.clone(),
subject: subject.clone(),
metadata: CredentialIssuerMetadata {
credential_issuer: issuer_url.clone(),
authorization_servers: vec![],
Expand All @@ -56,7 +49,7 @@ impl<S: Storage<CFC>, CFC: CredentialFormatCollection> CredentialIssuerManager<S
..Default::default()
},
},
subjects: Arc::new(Subjects::try_from(subjects)?),
subject,
storage,
listener: Arc::new(listener),
})
Expand Down
2 changes: 1 addition & 1 deletion oid4vc-manager/src/managers/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use oid4vp::{
// TODO: make VP/VC fromat agnostic. In current form only jwt_vp_json + jwt_vc_json are supported.
pub fn create_presentation_submission(
presentation_definition: &PresentationDefinition,
credentials: Vec<serde_json::Value>,
credentials: &[serde_json::Value],
) -> Result<PresentationSubmission> {
let id = "Submission ID".to_string();
let definition_id = presentation_definition.id().clone();
Expand Down
Loading

0 comments on commit a932af7

Please sign in to comment.