Skip to content

Commit

Permalink
fix: implement ES256 verification (#101)
Browse files Browse the repository at this point in the history
* refactor: undo unnecessary changes

* docs: list all config values

* refactor: rename config file

* refactor: remove example files from docker image

* refactor: merge config files

* refactor: update .dockerignore to include all Dockerfiles and .env files

* WIP: migrate from config macro to config function

* refactor: configs for `log_format` and `event_store`

* refactor: remove unused macros calls

* refactor: migrate `url` config

* refactor: `secret_manager` config

* refactor: `credential_configurations` config

* refactor: rename `DidMethodOptions`, add config for `signing_algorithms_supported`

* refactor: determine default DID method

* refactor: remove comments, load env variables

* WIP: refactor `event_publishers` config

* refactor: remove `metadata`

* refactor: remove `config!` macro

* refactor: rename `config_2` to `config`

* chore: change example logo, disable `event_publisher`, respect `default_did_method`

* chore: resolve clippy issues

* refactor: remove `set_metadata_configuration`

* refactor: remove `TEST_METADATA`

* WIP

* test: fix tests

* ci: update docker-compose

* fix: replace `localhost` with container name

* refactor: clean up code

* chore: fix unused import

* chore: remove unused deployment scripts

* refactor: read `connection_string` inside postgres module

* refactor: rename env prefix to `UNICORE`

* feat: init `IsuanceService`

* refacotr: remove unused code

* feat: add `from_jsonwebtoken_algorithm_to_jwsalgorithm` helper function

* chore: remove unused code

* feat: add `generate_stronghold` option

* feat: implement `ES256` verification

* chore: update logo URI's in example config file

* chore: remove temp stronghold file

* chore: remove `example-config.yaml` file

* chore: remove unused `identity_iota` dependencies

* refactor: remove unused 'services'

* refactor: use `from_jsonwebtoken_algorithm_to_jwsalgorithm`

* fix: reset config source to `agent_application/config.yaml`

* feat: update config files

* chore: update docker-compose file

* chore: update docker-compose file

* chore: remove commented code

* fix: don't use `example.config.yaml` by default

* fix: use `get_preferred_signing_algorithm`

* fix: add tests

---------

Co-authored-by: Daniel Mader <daniel.mader@impierce.com>
  • Loading branch information
nanderstabel and daniel-mader authored Aug 7, 2024
1 parent 47f4f82 commit 6c934b8
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 24 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions agent_issuance/src/credential/aggregate.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use agent_shared::config::{config, get_preferred_did_method};
use agent_shared::config::{config, get_preferred_did_method, get_preferred_signing_algorithm};
use async_trait::async_trait;
use cqrs_es::Aggregate;
use derivative::Derivative;
use identity_core::convert::FromJson;
use identity_credential::credential::{
Credential as W3CVerifiableCredential, CredentialBuilder as W3CVerifiableCredentialBuilder, Issuer,
};
use jsonwebtoken::{Algorithm, Header};
use jsonwebtoken::Header;
use oid4vc_core::jwt;
use oid4vci::credential_format_profiles::w3c_verifiable_credentials::jwt_vc_json::{
CredentialDefinition, JwtVcJson, JwtVcJsonParameters,
Expand Down Expand Up @@ -179,7 +179,7 @@ impl Aggregate for Credential {

let issuer_did = services
.issuer
.identifier(&default_did_method.to_string(), Algorithm::EdDSA)
.identifier(&default_did_method.to_string(), get_preferred_signing_algorithm())
.await
.unwrap();
let signed_credential = {
Expand Down Expand Up @@ -211,7 +211,7 @@ impl Aggregate for Credential {

json!(jwt::encode(
services.issuer.clone(),
Header::new(Algorithm::EdDSA),
Header::new(get_preferred_signing_algorithm()),
VerifiableCredentialJwt::builder()
.sub(subject_id)
.iss(issuer_did)
Expand Down Expand Up @@ -261,6 +261,7 @@ pub mod credential_tests {

use super::*;

use jsonwebtoken::Algorithm;
use lazy_static::lazy_static;
use oid4vci::proof::KeyProofMetadata;
use oid4vci::ProofType;
Expand Down
3 changes: 3 additions & 0 deletions agent_secret_manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ identity_iota.workspace = true
jsonwebtoken = "9.3"
log = "0.4"
oid4vc-core.workspace = true
p256 = { version = "0.13", features = ["jwk"] }
serde.workspace = true
serde_json = "1.0"
tokio.workspace = true
Expand All @@ -25,3 +26,5 @@ url.workspace = true
agent_shared = { path = "../agent_shared", features = ["test_utils"] }

futures.workspace = true
lazy_static.workspace = true
ring = "0.17.8"
106 changes: 91 additions & 15 deletions agent_secret_manager/src/subject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,31 @@ impl Verify for Subject {
.unwrap();

// Try decode from `MethodData` directly, else use public JWK params.
verification_method
.data()
.try_decode()
.or_else(|_| {
verification_method
.data()
.public_key_jwk()
.and_then(|public_key_jwk| match public_key_jwk.params() {
JwkParams::Okp(okp_params) => Some(okp_params.x.as_bytes().to_vec()),
JwkParams::Ec(ec_params) => Some(ec_params.x.as_bytes().to_vec()),
_ => None,
})
.ok_or(anyhow::anyhow!("Failed to decode public key for DID URL: {}", did_url))
})
.and_then(|encoded_public_key| URL_SAFE_NO_PAD.decode(encoded_public_key).map_err(Into::into))
verification_method.data().try_decode().or_else(|_| {
verification_method
.data()
.public_key_jwk()
.and_then(|public_key_jwk| match public_key_jwk.params() {
JwkParams::Okp(okp_params) => URL_SAFE_NO_PAD.decode(&okp_params.x).ok(),
JwkParams::Ec(ec_params) => {
let x_bytes = URL_SAFE_NO_PAD.decode(&ec_params.x).ok()?;
let y_bytes = URL_SAFE_NO_PAD.decode(&ec_params.y).ok()?;

let encoded_point = p256::EncodedPoint::from_affine_coordinates(
p256::FieldBytes::from_slice(&x_bytes),
p256::FieldBytes::from_slice(&y_bytes),
false, // false for uncompressed point
);

let verifying_key = p256::ecdsa::VerifyingKey::from_encoded_point(&encoded_point)
.expect("Failed to create verifying key from encoded point");

Some(verifying_key.to_encoded_point(false).as_bytes().to_vec())
}
_ => None,
})
.ok_or(anyhow::anyhow!("Failed to decode public key for DID URL: {}", did_url))
})
}
}

Expand Down Expand Up @@ -131,3 +141,69 @@ impl oid4vc_core::Subject for Subject {
fn origin() -> url::Origin {
config().url.parse::<url::Url>().unwrap().origin()
}

#[cfg(test)]
mod tests {
use super::*;
use agent_shared::config::{set_config, SecretManagerConfig};
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED, ED25519};

const ES256_SIGNED_JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5pSXNJbU55ZGlJNklsQXRNalUySWl3aWEybGtJam9pTkVGMVdXaFNRMk5HYkc0eWJuUm5VMTlxT1hCRlFtUkxkekl3VUhRdGJHRnFXVWh0V1RkQk1FMUdUU0lzSW10MGVTSTZJa1ZESWl3aWVDSTZJakpNV0dwT1JFOTZWM1J3WlZOWk0ydGlUbEkyWm14YVRVUjRZV2gxYXpKMlVXMWpkWFprUVRodk5EUWlMQ0o1SWpvaVpFRjJSVlpzV0UxSFVFdGFjMnRXV1RSWlZ6QnpPRUk0UzNZM2Myc3hZemt5VDA1WVJFcHZlRjlJY3lKOSMwIn0.eyJpc3MiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU5FRjFXV2hTUTJOR2JHNHliblJuVTE5cU9YQkZRbVJMZHpJd1VIUXRiR0ZxV1VodFdUZEJNRTFHVFNJc0ltdDBlU0k2SWtWRElpd2llQ0k2SWpKTVdHcE9SRTk2VjNSd1pWTlpNMnRpVGxJMlpteGFUVVI0WVdoMWF6SjJVVzFqZFhaa1FUaHZORFFpTENKNUlqb2laRUYyUlZac1dFMUhVRXRhYzJ0V1dUUlpWekJ6T0VJNFMzWTNjMnN4WXpreVQwNVlSRXB2ZUY5SWN5SjkiLCJzdWIiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaU5FRjFXV2hTUTJOR2JHNHliblJuVTE5cU9YQkZRbVJMZHpJd1VIUXRiR0ZxV1VodFdUZEJNRTFHVFNJc0ltdDBlU0k2SWtWRElpd2llQ0k2SWpKTVdHcE9SRTk2VjNSd1pWTlpNMnRpVGxJMlpteGFUVVI0WVdoMWF6SjJVVzFqZFhaa1FUaHZORFFpTENKNUlqb2laRUYyUlZac1dFMUhVRXRhYzJ0V1dUUlpWekJ6T0VJNFMzWTNjMnN4WXpreVQwNVlSRXB2ZUY5SWN5SjkiLCJhdWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOaUlzSW1OeWRpSTZJbEF0TWpVMklpd2lhMmxrSWpvaVlrNDNiSEpaWVhOUlZrNDNMVUpZY0MxMFdFVldTR1l0YVhkTWRsVnRiWHByVUZsc2VHWlRWRkZvVlNJc0ltdDBlU0k2SWtWRElpd2llQ0k2SW1odVkyNU5UM2sxU0dGWGJ6SmFTbmhCWW5sWU1GOW1NVTFHU1dsMlRrRmtUMjFXYjNSWGVWZG9ielFpTENKNUlqb2libE5wYkhwMllsTmFYMUp1VWpOU2RreHdkRWxITmpkVWJWVkVhR1ZQWVZGNlltczJhVFJmWDBkeVFTSjkiLCJleHAiOjE3MjMwMjkyMjUsImlhdCI6MTcyMzAyODYyNSwibm9uY2UiOiJ0aGlzIGlzIGEgbm9uY2UifQ.w202CZKOeGM9k35tysJylksBUGI3fvkOgsPPVrfXYZzurns7KF5plMiR_KHH4H_GpYg57Nf2JWa3YEcXGDTVdw";
const EDDSA_SIGNED_JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJhV1FpT2lKSmJWOVpNRkZQTm05SFgyczVNbTlzY1RWTWRIUTJZVkE0YzE5QmJFRmhWVUl6UzBkelVFY3RlR0kwSWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SWxaUGFrUjBRblozY0daalNraHlUelpMVjFOUGRYTlZVR1ptUWt3eVIxOUtjWFp0VVRZNFMzaDRWalFpZlEjMCJ9.eyJpc3MiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlpFUlRRU0lzSW1OeWRpSTZJa1ZrTWpVMU1Ua2lMQ0pyYVdRaU9pSkpiVjlaTUZGUE5tOUhYMnM1TW05c2NUVk1kSFEyWVZBNGMxOUJiRUZoVlVJelMwZHpVRWN0ZUdJMElpd2lhM1I1SWpvaVQwdFFJaXdpZUNJNklsWlBha1IwUW5aM2NHWmpTa2h5VHpaTFYxTlBkWE5WVUdabVFrd3lSMTlLY1hadFVUWTRTM2g0VmpRaWZRIiwic3ViIjoiZGlkOmp3azpleUpoYkdjaU9pSkZaRVJUUVNJc0ltTnlkaUk2SWtWa01qVTFNVGtpTENKcmFXUWlPaUpKYlY5Wk1GRlBObTlIWDJzNU1tOXNjVFZNZEhRMllWQTRjMTlCYkVGaFZVSXpTMGR6VUVjdGVHSTBJaXdpYTNSNUlqb2lUMHRRSWl3aWVDSTZJbFpQYWtSMFFuWjNjR1pqU2toeVR6WkxWMU5QZFhOVlVHWm1Ra3d5UjE5S2NYWnRVVFk0UzNoNFZqUWlmUSIsImF1ZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGWkVSVFFTSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSnJhV1FpT2lKdFFqSXhUV2t5Y1V0WVZtTTFOREpVWWt0U09UZ3lUelpUWjFKWVZrWlFaVzV3TTNGWWRIRlRla3R2SWl3aWEzUjVJam9pVDB0UUlpd2llQ0k2SWprM1JVRXpSSE5vUmpONlIwSllTVjlVYnpObVJrUnJNVTFxV1VaYVV6bFZiMUpVYmxCT1NIUlpVV01pZlEiLCJleHAiOjE3MjMwMzE3MTQsImlhdCI6MTcyMzAzMTExNCwibm9uY2UiOiJ0aGlzIGlzIGEgbm9uY2UifQ.oGRYpwH4QvWZs0bZkgAuxq6MqNYdoX44KxNfRl7GzXCnv_0D_c19rhYMwzn04R7udNCthFDr7GUhXLQgROlUDw";

lazy_static::lazy_static! {
static ref SECRET_MANAGER_CONFIG: SecretManagerConfig = SecretManagerConfig {
generate_stronghold: false,
stronghold_path: "../agent_secret_manager/tests/res/all_slots.stronghold".to_string(),
stronghold_password: "sup3rSecr3t".to_string(),
issuer_eddsa_key_id: Some("ed25519-0".to_string()),
issuer_es256_key_id: Some("es256-0".to_string()),
issuer_did: None,
issuer_fragment: None,
};
}

#[tokio::test]
async fn es256_signed_jwt_successfully_verified() {
set_config().set_secret_manager_config(SECRET_MANAGER_CONFIG.clone());

let subject = Arc::new(Subject {
secret_manager: crate::secret_manager().await,
});

let mut split = ES256_SIGNED_JWT.rsplitn(2, '.');
let (signature, message) = (split.next().unwrap(), split.next().unwrap());

// Decode the signature.
let signature_bytes = URL_SAFE_NO_PAD.decode(signature).unwrap();

// Resolve the public key from the DID Document
let public_key_bytes = subject.public_key("did:jwk:eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia2lkIjoiNEF1WWhSQ2NGbG4ybnRnU19qOXBFQmRLdzIwUHQtbGFqWUhtWTdBME1GTSIsImt0eSI6IkVDIiwieCI6IjJMWGpORE96V3RwZVNZM2tiTlI2ZmxaTUR4YWh1azJ2UW1jdXZkQThvNDQiLCJ5IjoiZEF2RVZsWE1HUEtac2tWWTRZVzBzOEI4S3Y3c2sxYzkyT05YREpveF9IcyJ9#0").await.unwrap();

// Verify the signature
let public_key = UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, public_key_bytes);
assert!(public_key.verify(message.as_bytes(), &signature_bytes).is_ok());
}

#[tokio::test]
async fn eddsa_signed_jwt_successfully_verified() {
set_config().set_secret_manager_config(SECRET_MANAGER_CONFIG.clone());

let subject = Arc::new(Subject {
secret_manager: crate::secret_manager().await,
});

let mut split = EDDSA_SIGNED_JWT.rsplitn(2, '.');
let (signature, message) = (split.next().unwrap(), split.next().unwrap());

// Decode the signature.
let signature_bytes = URL_SAFE_NO_PAD.decode(signature).unwrap();

// Resolve the public key from the DID Document
let public_key_bytes = subject.public_key("did:jwk:eyJhbGciOiJFZERTQSIsImNydiI6IkVkMjU1MTkiLCJraWQiOiJJbV9ZMFFPNm9HX2s5Mm9scTVMdHQ2YVA4c19BbEFhVUIzS0dzUEcteGI0Iiwia3R5IjoiT0tQIiwieCI6IlZPakR0QnZ3cGZjSkhyTzZLV1NPdXNVUGZmQkwyR19KcXZtUTY4S3h4VjQifQ#0").await.unwrap();

// Verify the signature
let public_key = UnparsedPublicKey::new(&ED25519, public_key_bytes);
assert!(public_key.verify(message.as_bytes(), &signature_bytes).is_ok());
}
}
Binary file not shown.
4 changes: 4 additions & 0 deletions agent_shared/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ impl ApplicationConfiguration {
}
}
}

pub fn set_secret_manager_config(&mut self, config: SecretManagerConfig) {
self.secret_manager = config;
}
}

/// Returns the application configuration or loads it, if it hasn't been loaded already.
Expand Down
5 changes: 3 additions & 2 deletions agent_shared/src/domain_linkage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod verifiable_credential_jwt;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::config::get_preferred_signing_algorithm;
use crate::error::SharedError;
use crate::from_jsonwebtoken_algorithm_to_jwsalgorithm;
use did_manager::SecretManager;
Expand All @@ -12,7 +13,7 @@ use identity_credential::domain_linkage::{DomainLinkageConfiguration, DomainLink
use identity_did::DID;
use identity_document::document::CoreDocument;
use identity_storage::{JwkDocumentExt, JwsSignatureOptions, Storage};
use jsonwebtoken::{Algorithm, Header};
use jsonwebtoken::Header;
use tracing::info;
use verifiable_credential_jwt::VerifiableCredentialJwt;

Expand Down Expand Up @@ -86,7 +87,7 @@ pub async fn create_did_configuration_resource(

// Compose JWT
let header = Header {
alg: Algorithm::EdDSA,
alg: get_preferred_signing_algorithm(),
typ: Some("JWT".to_string()),
kid: Some(format!("{subject_did}#key-0")),
..Default::default()
Expand Down
6 changes: 3 additions & 3 deletions agent_verification/src/authorization_request/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ use crate::{
generic_oid4vc::{GenericAuthorizationRequest, OID4VPAuthorizationRequest, SIOPv2AuthorizationRequest},
services::VerificationServices,
};
use agent_shared::config::config;
use agent_shared::config::{config, get_preferred_signing_algorithm};
use async_trait::async_trait;
use cqrs_es::Aggregate;
use jsonwebtoken::Algorithm;
use oid4vc_core::{authorization_request::ByReference, scope::Scope};
use oid4vp::authorization_request::ClientIdScheme;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -47,7 +46,7 @@ impl Aggregate for AuthorizationRequest {
let default_subject_syntax_type = services.relying_party.default_subject_syntax_type().to_string();
let verifier = &services.verifier;
let verifier_did = verifier
.identifier(&default_subject_syntax_type, Algorithm::EdDSA)
.identifier(&default_subject_syntax_type, get_preferred_signing_algorithm())
.await
.unwrap();

Expand Down Expand Up @@ -165,6 +164,7 @@ pub mod tests {
use agent_shared::config::set_config;
use agent_shared::config::SupportedDidMethod;
use cqrs_es::test::TestFramework;
use jsonwebtoken::Algorithm;
use lazy_static::lazy_static;
use oid4vc_core::Subject as _;
use oid4vc_core::{client_metadata::ClientMetadataResource, SubjectSyntaxType};
Expand Down

0 comments on commit 6c934b8

Please sign in to comment.