From 620e5d82eaa46da7e9562ea11bfa57a3552e0a5b Mon Sep 17 00:00:00 2001 From: Enrico Marconi Date: Tue, 23 Jan 2024 12:53:15 +0100 Subject: [PATCH] KbSdJwt validation and fmt --- bindings/grpc/proto/sd_jwt.proto | 10 +++ bindings/grpc/src/main.rs | 3 +- bindings/grpc/src/server.rs | 3 +- bindings/grpc/src/services/credential.rs | 32 ++++--- bindings/grpc/src/services/health_check.rs | 12 +-- bindings/grpc/src/services/mod.rs | 3 +- bindings/grpc/src/services/sd_jwt.rs | 90 +++++++++++++------ .../tests/api/credential_revocation_check.rs | 18 ++-- bindings/grpc/tests/api/health_check.rs | 4 +- bindings/grpc/tests/api/helpers.rs | 52 ++++++----- 10 files changed, 147 insertions(+), 80 deletions(-) diff --git a/bindings/grpc/proto/sd_jwt.proto b/bindings/grpc/proto/sd_jwt.proto index 4b02b6ed2b..6155855502 100644 --- a/bindings/grpc/proto/sd_jwt.proto +++ b/bindings/grpc/proto/sd_jwt.proto @@ -1,8 +1,18 @@ syntax = "proto3"; package sd_jwt; +message KeyBindingOptions { + optional string nonce = 1; + optional string aud = 2; + // TODO: add JWS validation options + optional string earliest_issuance_date = 3; + optional string latest_issuance_date = 4; + string holder_did = 5; +} + message VerificationRequest { string jwt = 1; + optional KeyBindingOptions kb_options = 2; } message VerificationResponse { diff --git a/bindings/grpc/src/main.rs b/bindings/grpc/src/main.rs index 76684a4e91..b98c0d2633 100644 --- a/bindings/grpc/src/main.rs +++ b/bindings/grpc/src/main.rs @@ -4,8 +4,7 @@ use iota_sdk::client::Client; #[tokio::main] async fn main() -> anyhow::Result<()> { - let api_endpoint = std::env::var("API_ENDPOINT") - .context("Missing environmental variable API_ENDPOINT")?; + let api_endpoint = std::env::var("API_ENDPOINT").context("Missing environmental variable API_ENDPOINT")?; let client: Client = Client::builder() .with_primary_node(&api_endpoint, None)? diff --git a/bindings/grpc/src/server.rs b/bindings/grpc/src/server.rs index 9f1923325d..1174573b38 100644 --- a/bindings/grpc/src/server.rs +++ b/bindings/grpc/src/server.rs @@ -1,7 +1,8 @@ use std::net::SocketAddr; use iota_sdk::client::Client; -use tonic::transport::server::{Router, Server}; +use tonic::transport::server::Router; +use tonic::transport::server::Server; use crate::services; diff --git a/bindings/grpc/src/services/credential.rs b/bindings/grpc/src/services/credential.rs index 3119e9f98e..1496dd075e 100644 --- a/bindings/grpc/src/services/credential.rs +++ b/bindings/grpc/src/services/credential.rs @@ -1,28 +1,36 @@ -use credential_verification::{ - credential_revocation_server::{CredentialRevocation, CredentialRevocationServer}, - RevocationCheckRequest, RevocationCheckResponse, RevocationStatus, -}; -use identity_iota::{ - credential::{self, JwtCredentialValidatorUtils, JwtValidationError, RevocationBitmapStatus}, - prelude::{IotaDocument, Resolver}, -}; +use credential_verification::credential_revocation_server::CredentialRevocation; +use credential_verification::credential_revocation_server::CredentialRevocationServer; +use credential_verification::RevocationCheckRequest; +use credential_verification::RevocationCheckResponse; +use credential_verification::RevocationStatus; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::JwtValidationError; +use identity_iota::credential::RevocationBitmapStatus; +use identity_iota::credential::{self}; +use identity_iota::prelude::IotaDocument; +use identity_iota::prelude::Resolver; use iota_sdk::client::Client; use prost::bytes::Bytes; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use serde::Serialize; use thiserror::Error; -use tonic::{self, Request, Response}; +use tonic::Request; +use tonic::Response; +use tonic::{self}; mod credential_verification { use super::RevocationCheckError; - use identity_iota::credential::{RevocationBitmapStatus, Status}; + use identity_iota::credential::RevocationBitmapStatus; + use identity_iota::credential::Status; tonic::include_proto!("credentials"); impl TryFrom for Status { type Error = RevocationCheckError; fn try_from(req: RevocationCheckRequest) -> Result { - use identity_iota::core::{Object, Url}; + use identity_iota::core::Object; + use identity_iota::core::Url; if req.r#type.as_str() != RevocationBitmapStatus::TYPE { Err(Self::Error::UnknownRevocationType(req.r#type)) diff --git a/bindings/grpc/src/services/health_check.rs b/bindings/grpc/src/services/health_check.rs index 16119a3d7c..21a80e7c25 100644 --- a/bindings/grpc/src/services/health_check.rs +++ b/bindings/grpc/src/services/health_check.rs @@ -1,8 +1,10 @@ -use health_check::{ - health_check_server::{HealthCheck, HealthCheckServer}, - HealthCheckRequest, HealthCheckResponse, -}; -use tonic::{Request, Response, Status}; +use health_check::health_check_server::HealthCheck; +use health_check::health_check_server::HealthCheckServer; +use health_check::HealthCheckRequest; +use health_check::HealthCheckResponse; +use tonic::Request; +use tonic::Response; +use tonic::Status; mod health_check { tonic::include_proto!("health_check"); diff --git a/bindings/grpc/src/services/mod.rs b/bindings/grpc/src/services/mod.rs index c4eca0a7f3..5eb5f49b67 100644 --- a/bindings/grpc/src/services/mod.rs +++ b/bindings/grpc/src/services/mod.rs @@ -3,7 +3,8 @@ pub mod health_check; pub mod sd_jwt; use iota_sdk::client::Client; -use tonic::transport::server::{Routes, RoutesBuilder}; +use tonic::transport::server::Routes; +use tonic::transport::server::RoutesBuilder; pub fn routes(client: Client) -> Routes { let mut routes = RoutesBuilder::default(); diff --git a/bindings/grpc/src/services/sd_jwt.rs b/bindings/grpc/src/services/sd_jwt.rs index 7e0e078a57..fd4b860931 100644 --- a/bindings/grpc/src/services/sd_jwt.rs +++ b/bindings/grpc/src/services/sd_jwt.rs @@ -1,23 +1,49 @@ -use _sd_jwt::{ - verification_server::{Verification, VerificationServer}, - VerificationRequest, VerificationResponse, -}; +use _sd_jwt::verification_server::Verification; +use _sd_jwt::verification_server::VerificationServer; +use _sd_jwt::VerificationRequest; +use _sd_jwt::VerificationResponse; use identity_eddsa_verifier::EdDSAJwsVerifier; -use identity_iota::{ - core::{Object, ToJson}, - credential::{FailFast, Jwt, JwtCredentialValidationOptions, JwtCredentialValidatorUtils, SdJwtCredentialValidator}, - iota::{IotaDID, IotaDocument}, - resolver::Resolver, - sd_jwt_payload::{Error as SdJwtError, SdJwt, SdObjectDecoder}, -}; +use identity_iota::core::Object; +use identity_iota::core::Timestamp; +use identity_iota::core::ToJson; +use identity_iota::credential::FailFast; +use identity_iota::credential::Jwt; +use identity_iota::credential::JwtCredentialValidationOptions; +use identity_iota::credential::JwtCredentialValidatorUtils; +use identity_iota::credential::KeyBindingJWTValidationOptions; +use identity_iota::credential::SdJwtCredentialValidator; +use identity_iota::iota::IotaDID; +use identity_iota::iota::IotaDocument; +use identity_iota::resolver::Resolver; +use identity_iota::sd_jwt_payload::SdJwt; +use identity_iota::sd_jwt_payload::SdObjectDecoder; use iota_sdk::client::Client; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use serde::Serialize; use thiserror::Error; +use self::_sd_jwt::KeyBindingOptions; + mod _sd_jwt { tonic::include_proto!("sd_jwt"); } +impl From for KeyBindingJWTValidationOptions { + fn from(value: KeyBindingOptions) -> Self { + let mut kb_options = Self::default(); + kb_options.nonce = value.nonce; + kb_options.aud = value.aud; + kb_options.earliest_issuance_date = value + .earliest_issuance_date + .and_then(|t| Timestamp::parse(t.as_str()).ok()); + kb_options.latest_issuance_date = value + .latest_issuance_date + .and_then(|t| Timestamp::parse(t.as_str()).ok()); + + kb_options + } +} + #[derive(Debug, Error, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "error", content = "reason")] @@ -30,15 +56,12 @@ enum SdJwtVerificationError { VerificationError(String), #[error("Failed to resolve DID Document: {0}")] DidResolutionError(String), -} - -impl From for SdJwtVerificationError { - fn from(value: SdJwtError) -> Self { - match value { - SdJwtError::DeserializationError(e) => Self::DeserializationError(e), - _ => unreachable!(), - } - } + #[error("Missing \"kb_options\".")] + MissingKbOptions, + #[error("{0}")] + KeyBindingJwtError(String), + #[error("Provided an invalid holder's id.")] + InvalidHolderDid, } impl From for tonic::Status { @@ -48,6 +71,9 @@ impl From for tonic::Status { SdJwtVerificationError::JwtError(_) => tonic::Code::InvalidArgument, SdJwtVerificationError::VerificationError(_) => tonic::Code::InvalidArgument, SdJwtVerificationError::DidResolutionError(_) => tonic::Code::NotFound, + SdJwtVerificationError::MissingKbOptions => tonic::Code::InvalidArgument, + SdJwtVerificationError::KeyBindingJwtError(_) => tonic::Code::Internal, + SdJwtVerificationError::InvalidHolderDid => tonic::Code::InvalidArgument, }; let message = value.to_string(); let error_json = serde_json::to_vec(&value).expect("plenty of memory!"); @@ -75,8 +101,8 @@ impl Verification for SdJwtService { &self, request: tonic::Request, ) -> Result, tonic::Status> { - let VerificationRequest { jwt } = request.into_inner(); - let mut sd_jwt = SdJwt::parse(&jwt).map_err(SdJwtVerificationError::from)?; + let VerificationRequest { jwt, kb_options } = request.into_inner(); + let mut sd_jwt = SdJwt::parse(&jwt).map_err(|e| SdJwtVerificationError::DeserializationError(e.to_string()))?; let jwt = Jwt::new(sd_jwt.jwt); let issuer_did = JwtCredentialValidatorUtils::extract_issuer_from_jwt::(&jwt) @@ -99,8 +125,22 @@ impl Verification for SdJwtService { ) .map_err(|e| SdJwtVerificationError::VerificationError(e.to_string()))?; - if let Some(kb_jwt) = sd_jwt.key_binding_jwt { - todo!(); + if sd_jwt.key_binding_jwt.is_some() { + let Some(kb_options) = kb_options else { + return Err(SdJwtVerificationError::MissingKbOptions.into()); + }; + let holder = { + let did = + IotaDID::parse(kb_options.holder_did.as_str()).map_err(|_| SdJwtVerificationError::InvalidHolderDid)?; + self + .resolver + .resolve(&did) + .await + .map_err(|e| SdJwtVerificationError::DidResolutionError(e.to_string()))? + }; + let _ = validator + .validate_key_binding_jwt(&sd_jwt, &holder, &kb_options.into()) + .map_err(|e| SdJwtVerificationError::KeyBindingJwtError(e.to_string()))?; } Ok(tonic::Response::new(VerificationResponse { diff --git a/bindings/grpc/tests/api/credential_revocation_check.rs b/bindings/grpc/tests/api/credential_revocation_check.rs index a752fe6bad..5b5de98a8d 100644 --- a/bindings/grpc/tests/api/credential_revocation_check.rs +++ b/bindings/grpc/tests/api/credential_revocation_check.rs @@ -1,14 +1,14 @@ -use credentials::{credential_revocation_client::CredentialRevocationClient, RevocationStatus}; -use identity_iota::{ - credential::{self, RevocationBitmap, RevocationBitmapStatus}, - did::DID, -}; +use credentials::credential_revocation_client::CredentialRevocationClient; +use credentials::RevocationStatus; +use identity_iota::credential::RevocationBitmap; +use identity_iota::credential::RevocationBitmapStatus; +use identity_iota::credential::{self}; +use identity_iota::did::DID; use serde_json::json; -use crate::{ - credential_revocation_check::credentials::RevocationCheckRequest, - helpers::{Entity, TestServer}, -}; +use crate::credential_revocation_check::credentials::RevocationCheckRequest; +use crate::helpers::Entity; +use crate::helpers::TestServer; mod credentials { tonic::include_proto!("credentials"); diff --git a/bindings/grpc/tests/api/health_check.rs b/bindings/grpc/tests/api/health_check.rs index a4ccb231a2..d25ce898a6 100644 --- a/bindings/grpc/tests/api/health_check.rs +++ b/bindings/grpc/tests/api/health_check.rs @@ -1,4 +1,6 @@ -use health_check::{health_check_client::HealthCheckClient, HealthCheckRequest, HealthCheckResponse}; +use health_check::health_check_client::HealthCheckClient; +use health_check::HealthCheckRequest; +use health_check::HealthCheckResponse; use crate::helpers::TestServer; diff --git a/bindings/grpc/tests/api/helpers.rs b/bindings/grpc/tests/api/helpers.rs index 5532a9f9db..818d1ac99f 100644 --- a/bindings/grpc/tests/api/helpers.rs +++ b/bindings/grpc/tests/api/helpers.rs @@ -1,28 +1,32 @@ use anyhow::Context; -use identity_iota::{ - iota::{IotaClientExt, IotaDocument, IotaIdentityClientExt, NetworkName}, - verification::{jws::JwsAlgorithm, MethodScope}, -}; -use identity_storage::{key_id_storage::KeyIdMemstore, key_storage::JwkMemStore, JwkDocumentExt, Storage}; -use iota_sdk::{ - client::{ - api::GetAddressesOptions, - node_api::indexer::query_parameters::QueryParameter, - secret::{stronghold::StrongholdSecretManager, SecretManager}, - Client, Password, - }, - crypto::keys::bip39, - types::block::{ - address::{Address, Bech32Address, Hrp}, - output::AliasOutputBuilder, - }, -}; -use rand::{ - distributions::{Alphanumeric, DistString}, - thread_rng, -}; -use std::{net::SocketAddr, path::PathBuf}; -use tokio::{net::TcpListener, task::JoinHandle}; +use identity_iota::iota::IotaClientExt; +use identity_iota::iota::IotaDocument; +use identity_iota::iota::IotaIdentityClientExt; +use identity_iota::iota::NetworkName; +use identity_iota::verification::jws::JwsAlgorithm; +use identity_iota::verification::MethodScope; +use identity_storage::key_id_storage::KeyIdMemstore; +use identity_storage::key_storage::JwkMemStore; +use identity_storage::JwkDocumentExt; +use identity_storage::Storage; +use iota_sdk::client::api::GetAddressesOptions; +use iota_sdk::client::node_api::indexer::query_parameters::QueryParameter; +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +use iota_sdk::client::secret::SecretManager; +use iota_sdk::client::Client; +use iota_sdk::client::Password; +use iota_sdk::crypto::keys::bip39; +use iota_sdk::types::block::address::Address; +use iota_sdk::types::block::address::Bech32Address; +use iota_sdk::types::block::address::Hrp; +use iota_sdk::types::block::output::AliasOutputBuilder; +use rand::distributions::Alphanumeric; +use rand::distributions::DistString; +use rand::thread_rng; +use std::net::SocketAddr; +use std::path::PathBuf; +use tokio::net::TcpListener; +use tokio::task::JoinHandle; use tonic::transport::Uri; pub type MemStorage = Storage;