Crates.io | siopv2 |
lib.rs | siopv2 |
version | 0.1.0 |
source | src |
created_at | 2023-06-12 12:16:55.933545 |
updated_at | 2023-06-12 12:16:55.933545 |
description | Rust implementation for the OpenID Connect Self-Issued OpenID Provider v2 standard |
homepage | https://www.impierce.com/ |
repository | https://github.com/impierce/openid4vc |
max_upload_size | |
id | 888134 |
size | 96,956 |
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:
OpenID for Verifiable Credential Issuance (OID4VCI) – Defines an API and corresponding OAuth-based authorization mechanisms for issuance of Verifiable Credentials
OpenID for Verifiable Presentations (OID4VP) – Defines a mechanism on top of OAuth 2.0 to allow presentation of claims in the form of Verifiable Credentials as part of the protocol flow
Self-Issued OpenID Provider v2 (SIOPv2) – Enables End-Users to use OpenID Providers (OPs) that they control
OpenID for Verifiable Presentations over BLE – Enables using Bluetooth Low Energy (BLE) to request the presentation of verifiable credentials. It uses the request and response syntax as defined in OID4VP.
Currently the Implicit Flow is consists of four major parts:
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.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.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.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
.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);
}
// A Subject type that can be ingested by a Provider
#[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())
}
}
// A Validator type that can be ingested by a RelyingParty
#[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() {
// Create a new mock server and retreive it's url.
let mock_server = MockServer::start().await;
let server_url = mock_server.uri();
// Create a new validator.
let validator = MySubject::default();
// Create a new relying party.
let relying_party = RelyingParty::new(validator);
// Create a new RequestUrl with response mode `post` for cross-device communication.
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();
// Create a new `request_uri` endpoint on the mock server and load it with the JWT encoded `AuthorizationRequest`.
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;
// Create a new `redirect_uri` endpoint on the mock server where the `Provider` will send the `AuthorizationResponse`.
Mock::given(method("POST"))
.and(path("/redirect_uri"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
// Create a new subject.
let subject = MySubject::default();
// Create a new provider.
let provider = Provider::new(subject).await.unwrap();
// Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint.
let request_url = RequestUrl::builder()
.request_uri(format!("{server_url}/request_uri"))
.build()
.unwrap();
// The Provider obtains the reuquest url either by a deeplink or by scanning a QR code. It then validates the
// request. Since in this case the request is a JWT, the provider will fetch the request by sending a GET
// request to mock server's `request_uri` endpoint.
let request = provider.validate_request(request_url).await.unwrap();
// Assert that the request was successfully received by the mock server at the `request_uri` endpoint.
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 the provider generate a response based on the validated request. The response is an `IdToken` which is
// encoded as a JWT.
let response = provider
.generate_response(
request,
StandardClaims {
name: Some(ClaimValue("Jane Doe".to_string())),
..Default::default()
},
)
.await
.unwrap();
// The provider sends it's response to the mock server's `redirect_uri` endpoint.
provider.send_response(response).await.unwrap();
// Assert that the AuthorizationResponse was successfully received by the mock server at the expected endpoint.
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();
// The `RelyingParty` then validates the response by decoding the header of the id_token, by fetching the public
// key corresponding to the key identifier and finally decoding the id_token using the public key and by
// validating the signature.
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()
}
);
}