1个不稳定版本
0.1.0 | 2023年6月12日 |
---|
#1080 in 密码学
用于 oid4vc
97KB
2K SLoC
openid4vc
这个库旨在支持OpenID for Verifiable Credentials工作下的所有规范。
OpenID for Verifiable Credentials (OID4VC) 包含以下规范
-
OpenID for Verifiable Credential Issuance (OID4VCI) – 定义了发行可验证凭证的API及其基于OAuth的授权机制
-
OpenID for Verifiable Presentations (OID4VP) – 定义了OAuth 2.0之上的机制,允许在协议流程中展示以可验证凭证形式呈现的声明
-
Self-Issued OpenID Provider v2 (SIOPv2) – 允许最终用户使用他们控制的OpenID提供者(OP)
-
OpenID for Verifiable Presentations over BLE – 允许使用蓝牙低功耗(BLE)请求展示可验证凭证。它使用OID4VP中定义的请求和响应语法。
描述
目前隐式流程由以下四个主要部分组成
- 一个可以接受
AuthorizationRequest
并生成AuthorizationResponse
的Provider
,通过创建一个IdToken
,将其密钥标识符添加到id_token
的头部,对id_token
进行签名,并将其包装到AuthorizationResponse
中。它还可以通过redirect_uri
参数发送AuthorizationResponse
。 - 一个
RelyingParty
结构,它可以验证AuthorizationResponse
,通过使用密钥标识符(从id_token
中提取)和其公钥来验证其IdToken
。 - 可以将代表DID方法的签名逻辑的自定义结构体实现为
Subject
特质。一个Provider
可以摄入实现Subject
特质的对象,以便在生成AuthorizationResponse
时,可以使用特定Subject
的DID方法语法、密钥标识和签名方法。 - 可以将代表DID方法的验证逻辑的自定义结构体实现为
Validator
特质。当被RelyingParty
摄入时,它可以解析出用于验证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()
}
);
}
依赖项
~18–34MB
~618K SLoC