openid4vc
This library aims to support all the specifications under the OpenID for Verifiable Credentials
works.
OpenID for Verifiable Credentials (OID4VC) consists of the following specifications:
Description
Currently the Implicit Flow is consists of four major parts:
- A
Provider that can accept a AuthorizationRequest and generate a AuthorizationResponse by creating an IdToken, adding its key identifier to the header of the id_token, signing the id_token and wrap it into a AuthorizationResponse. It can also send the AuthorizationResponse using the redirect_uri parameter.
- A
RelyingParty struct which can validate a AuthorizationResponse by validating its IdToken using a key identifier (which is extracted from the id_token) and its public key.
- The
Subject trait can be implemented on a custom struct representing the signing logic of a DID method. A Provider can ingest an object that implements the Subject trait so that during generation of a AuthorizationResponse the DID method syntax, key identifier and signing method of the specific Subject can be used.
- The
Validator trait can be implemented on a custom struct representing the validating logic of a DID method. When ingested by a RelyingParty, it can resolve the public key that is needed for validating an IdToken.
Example
use anyhow::Result;
use async_trait::async_trait;
use chrono::{Duration, Utc};
use ed25519_dalek::{Keypair, Signature, Signer};
use lazy_static::lazy_static;
use siopv2::{
claims::{ClaimRequests, ClaimValue, IndividualClaimRequest},
request::ResponseType,
Provider, Registration, RelyingParty, RequestUrl, AuthorizationResponse, Scope, AuthorizationRequest, StandardClaims, Subject, Validator,
};
use rand::rngs::OsRng;
use wiremock::{
http::Method,
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
};
lazy_static! {
pub static ref MOCK_KEYPAIR: Keypair = Keypair::generate(&mut OsRng);
}
#[derive(Default)]
pub struct MySubject;
impl MySubject {
pub fn new() -> Self {
MySubject {}
}
}
#[async_trait]
impl Subject for MySubject {
fn did(&self) -> Result<did_url::DID> {
Ok(did_url::DID::parse("did:mymethod:subject")?)
}
fn key_identifier(&self) -> Option<String> {
Some("key_identifier".to_string())
}
async fn sign<'a>(&self, message: &'a str) -> Result<Vec<u8>> {
let signature: Signature = MOCK_KEYPAIR.sign(message.as_bytes());
Ok(signature.to_bytes().to_vec())
}
}
#[async_trait]
impl Validator for MySubject {
async fn public_key<'a>(&self, _kid: &'a str) -> Result<Vec<u8>> {
Ok(MOCK_KEYPAIR.public.to_bytes().to_vec())
}
}
#[derive(Default)]
pub struct MyValidator;
#[async_trait]
impl Validator for MyValidator {
async fn public_key<'a>(&self, _kid: &'a str) -> Result<Vec<u8>> {
Ok(MOCK_KEYPAIR.public.to_bytes().to_vec())
}
}
#[tokio::main]
async fn main() {
let mock_server = MockServer::start().await;
let server_url = mock_server.uri();
let validator = MySubject::default();
let relying_party = RelyingParty::new(validator);
let request: AuthorizationRequest = RequestUrl::builder()
.response_type(ResponseType::IdToken)
.client_id("did:mymethod:relyingparty".to_string())
.scope(Scope::openid())
.redirect_uri(format!("{server_url}/redirect_uri"))
.response_mode("post".to_string())
.registration(
Registration::default()
.with_subject_syntax_types_supported(vec!["did:mymethod".to_string()])
.with_id_token_signing_alg_values_supported(vec!["EdDSA".to_string()]),
)
.claims(ClaimRequests {
id_token: Some(StandardClaims {
name: Some(IndividualClaimRequest::default()),
..Default::default()
}),
..Default::default()
})
.exp((Utc::now() + Duration::minutes(10)).timestamp())
.nonce("n-0S6_WzA2Mj".to_string())
.build()
.and_then(TryInto::try_into)
.unwrap();
Mock::given(method("GET"))
.and(path("/request_uri"))
.respond_with(ResponseTemplate::new(200).set_body_string(relying_party.encode(&request).await.unwrap()))
.mount(&mock_server)
.await;
Mock::given(method("POST"))
.and(path("/redirect_uri"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let subject = MySubject::default();
let provider = Provider::new(subject).await.unwrap();
let request_url = RequestUrl::builder()
.request_uri(format!("{server_url}/request_uri"))
.build()
.unwrap();
let request = provider.validate_request(request_url).await.unwrap();
let get_request = mock_server.received_requests().await.unwrap()[0].clone();
assert_eq!(get_request.method, Method::Get);
assert_eq!(get_request.url.path(), "/request_uri");
let response = provider
.generate_response(
request,
StandardClaims {
name: Some(ClaimValue("Jane Doe".to_string())),
..Default::default()
},
)
.await
.unwrap();
provider.send_response(response).await.unwrap();
let post_request = mock_server.received_requests().await.unwrap()[1].clone();
assert_eq!(post_request.method, Method::Post);
assert_eq!(post_request.url.path(), "/redirect_uri");
let response: AuthorizationResponse = serde_urlencoded::from_bytes(post_request.body.as_slice()).unwrap();
let id_token = relying_party.validate_response(&response).await.unwrap();
assert_eq!(
id_token.standard_claims(),
&StandardClaims {
name: Some(ClaimValue("Jane Doe".to_string())),
..Default::default()
}
);
}