Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for using multiple DID methods #70

Merged
merged 6 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
nanderstabel marked this conversation as resolved.
Show resolved Hide resolved
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