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 pre-authorized code flow #44

Merged
merged 29 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
339577c
feat: add first pre-authorized code flow
nanderstabel Jul 3, 2023
606ebd7
WIP
nanderstabel Jul 21, 2023
5a75380
fix: fix
nanderstabel Jul 21, 2023
881b00d
fix rebase
nanderstabel Jul 21, 2023
924462a
fix rebase
nanderstabel Jul 21, 2023
cf76bb7
fix rebase
nanderstabel Jul 21, 2023
41898de
feat: Add credential issuer manager
nanderstabel Jul 21, 2023
614b8c4
feat: add server
nanderstabel Jul 22, 2023
533b596
feat: init authorization code
nanderstabel Jul 24, 2023
c176415
style: clean up code
nanderstabel Jul 24, 2023
0934f3e
style: add newline
nanderstabel Jul 24, 2023
0a8f422
feat: Add order parameter to JwtVcJson
nanderstabel Jul 24, 2023
c8e739b
style: clean code
nanderstabel Jul 24, 2023
7d959f3
style: several improvements
nanderstabel Jul 24, 2023
93f384b
add tests and comments
nanderstabel Jul 24, 2023
001e283
fix clippy
nanderstabel Jul 24, 2023
e16a8a1
feat: improve authorization_details + unit tests
nanderstabel Jul 25, 2023
159f453
tests: add unit tests for credentials_supported
nanderstabel Jul 25, 2023
2a7b648
feat: add CredentialFormatCollection
nanderstabel Jul 25, 2023
7c79cf3
feat: add jwt_vc_json_ld and unit tests
nanderstabel Jul 25, 2023
8eab874
test: add unit test for credential issuer metadata
nanderstabel Jul 25, 2023
547cc46
fix: remove unused dependencies
nanderstabel Jul 25, 2023
c1d8acb
style: Add newline
nanderstabel Jul 25, 2023
46e7129
feat: add skip_serializing_none and server improve
nanderstabel Jul 27, 2023
4807561
fix: fix reqwest
nanderstabel Jul 28, 2023
30f6b75
feat: set default CFC = CredentialFormats
nanderstabel Aug 1, 2023
bacb43f
feat: add layer to Server
nanderstabel Aug 2, 2023
8d75a0b
fix state
nanderstabel Aug 2, 2023
35bafbd
style: use join for metadata endpoints
nanderstabel Aug 9, 2023
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ tokio = { version = "1.26.0", features = ["rt", "macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
serde_with = "3.0"
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] }
url = { version = "2.3", features = ["serde"] }
getset = "0.1"
identity_credential = { git = "https://[email protected]/iotaledger/identity.rs", rev = "4f27434", default-features = false, features = ["validator", "credential", "presentation"] }
Expand Down
11 changes: 2 additions & 9 deletions oid4vc-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@ version = "0.1.0"
edition = "2021"

[dependencies]
tokio.workspace = true
serde.workspace = true
serde_json = "1.0"
serde_with = "2.3"
anyhow = "1.0.70"
chrono = "0.4.24"
getset = "0.1.2"
jsonwebtoken = "8.2.0"
reqwest = { version = "0.11.14", default-features = false, features = ["json", "rustls-tls"] }
base64-url = "2.0.0"
async-trait = "0.1.68"
did_url = "0.1.0"
url = { version = "2.3.1", features = ["serde"] }
is_empty = "0.2.0"
serde_urlencoded = "0.7.1"
derive_more = "0.99.16"
identity_credential.workspace = true
futures = "0.3"
rand = "0.7"

[dev-dependencies]
ed25519-dalek = "1.0.1"
rand = "0.7"
lazy_static = "1.4.0"
derivative = "2.2.0"
tokio.workspace = true
6 changes: 4 additions & 2 deletions oid4vc-core/src/authentication/subject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::{Collection, Sign, SubjectSyntaxType, Verify};
use anyhow::Result;
use std::{str::FromStr, 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 {
Expand All @@ -28,8 +30,8 @@ impl<const N: usize> TryFrom<[Arc<dyn Subject>; N]> for Subjects {
Ok(Self::from(
subjects
.iter()
.map(|subject| (subject.type_().unwrap(), subject.clone()))
.collect::<Vec<_>>(),
.map(|subject| subject.type_().map(|subject_type| (subject_type, subject.clone())))
.collect::<Result<Vec<_>>>()?,
))
}
}
1 change: 1 addition & 0 deletions oid4vc-core/src/collection.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::SubjectSyntaxType;
use std::{collections::HashMap, sync::Arc};

#[derive(Clone)]
pub struct Collection<T: ?Sized>(pub HashMap<SubjectSyntaxType, Arc<T>>);

impl<T: ?Sized> Collection<T> {
Expand Down
14 changes: 5 additions & 9 deletions oid4vc-core/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ impl<C> JsonWebToken<C>
where
C: Serialize,
{
pub fn new(payload: C) -> Self {
JsonWebToken {
// TODO: Undo hardcoding and consider not using the jsonwebtoken crate.
header: Header::new(Algorithm::EdDSA),
payload,
}
pub fn new(header: Header, payload: C) -> Self {
JsonWebToken { header, payload }
}

pub fn kid(mut self, kid: String) -> Self {
Expand All @@ -51,14 +47,14 @@ where
Ok(jsonwebtoken::decode::<T>(jwt, &key, &Validation::new(algorithm))?.claims)
}

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

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

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

Expand Down Expand Up @@ -95,7 +91,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), claims).unwrap();
let encoded = encode(Arc::new(subject), Header::new(Algorithm::EdDSA), claims).unwrap();

let verifier = MockVerifier::new();
let (kid, algorithm) = extract_header(&encoded).unwrap();
Expand Down
27 changes: 26 additions & 1 deletion oid4vc-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ pub use authentication::{
};
pub use collection::Collection;
pub use decoder::Decoder;
use rand::{distributions::Alphanumeric, Rng};
pub use rfc7519_claims::RFC7519Claims;
use serde::Serialize;
pub use subject_syntax_type::{DidMethod, SubjectSyntaxType};

#[cfg(test)]
pub mod test_utils;
mod test_utils;

#[macro_export]
macro_rules! builder_fn {
Expand All @@ -36,3 +38,26 @@ macro_rules! builder_fn {
}
};
}

// Helper function that allows to serialize custom structs into a query string.
pub fn to_query_value<T: Serialize>(value: &T) -> anyhow::Result<String> {
serde_json::to_string(value)
.map(|s| s.chars().filter(|c| !c.is_whitespace()).collect::<String>())
.map_err(|e| e.into())
}

pub fn generate_authorization_code(length: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}

pub fn generate_nonce(length: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
8 changes: 8 additions & 0 deletions oid4vc-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,18 @@ identity_iota = "0.6"
identity_core = { git = "https://[email protected]/iotaledger/identity.rs", rev = "4f27434" }
identity_credential.workspace = true
futures = "0.3"
axum = "0.6"
axum-auth = "0.4"
reqwest.workspace = true
jsonwebtoken = "8.3"
paste = "1.0"
tower-http = { version = "0.4", features = ["cors"]}

[dev-dependencies]
ed25519-dalek = "1.0.1"
rand = "0.7"
lazy_static = "1.4.0"
derivative = "2.2.0"
wiremock = "0.5.18"
jwt = "0.16"

2 changes: 2 additions & 0 deletions oid4vc-manager/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod managers;
pub mod methods;
pub mod servers;
pub mod storage;

pub use managers::{provider::ProviderManager, relying_party::RelyingPartyManager};
83 changes: 83 additions & 0 deletions oid4vc-manager/src/managers/credential_issuer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::storage::Storage;
use anyhow::Result;
use oid4vc_core::{Subject, Subjects};
use oid4vci::{
credential_format_profiles::CredentialFormatCollection,
credential_issuer::{
authorization_server_metadata::AuthorizationServerMetadata,
credential_issuer_metadata::CredentialIssuerMetadata, CredentialIssuer,
},
credential_offer::{CredentialOffer, CredentialOfferQuery, CredentialsObject, Grants},
};
use reqwest::Url;
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 storage: S,
pub listener: Arc<TcpListener>,
}

impl<S: Storage<CFC> + Clone, CFC: CredentialFormatCollection> CredentialIssuerManager<S, CFC> {
pub fn new<const N: usize>(
listener: Option<TcpListener>,
storage: S,
subjects: [Arc<dyn Subject>; N],
) -> 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
.get(0)
.ok_or_else(|| anyhow::anyhow!("No subjects found."))?
.clone(),
metadata: CredentialIssuerMetadata {
credential_issuer: issuer_url.clone(),
authorization_server: None,
credential_endpoint: issuer_url.join("/credential")?,
batch_credential_endpoint: None,
deferred_credential_endpoint: None,
credentials_supported: storage.get_credentials_supported(),
display: None,
},
authorization_server_metadata: AuthorizationServerMetadata {
issuer: issuer_url.clone(),
authorization_endpoint: issuer_url.join("/authorize")?,
token_endpoint: issuer_url.join("/token")?,
..Default::default()
},
},
subjects: Arc::new(Subjects::try_from(subjects)?),
storage,
listener: Arc::new(listener),
})
}

pub fn credential_issuer_url(&self) -> Result<Url> {
Ok(self.credential_issuer.metadata.credential_issuer.clone())
}

pub fn credential_offer_uri(&self) -> Result<String> {
let credential = self
.credential_issuer
.metadata
.credentials_supported
.get(0)
.ok_or_else(|| anyhow::anyhow!("No credentials supported."))?
.credential_format
.clone();
Ok(CredentialOfferQuery::CredentialOffer(CredentialOffer {
credential_issuer: self.credential_issuer.metadata.credential_issuer.clone(),
credentials: vec![CredentialsObject::ByValue(credential)],
grants: Some(Grants {
authorization_code: self.storage.get_authorization_code(),
pre_authorized_code: self.storage.get_pre_authorized_code(),
}),
})
.to_string())
}
}
1 change: 1 addition & 0 deletions oid4vc-manager/src/managers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod credential_issuer;
pub mod presentation;
pub mod provider;
pub mod relying_party;
Loading