Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Did rotation handler #190

Merged
merged 29 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a781861
feat(): init did rotation handler
Christiantyemele Sep 25, 2024
0b03fb0
added jwt signature validation
Christiantyemele Sep 26, 2024
363d5bf
feat(): jwt verification
Christiantyemele Sep 26, 2024
cf37877
working on did_rotation test
Christiantyemele Sep 27, 2024
039c9fb
fix(): added test for did rotation
Christiantyemele Sep 27, 2024
97b0f01
did rotation unit testing
Christiantyemele Sep 30, 2024
45c1434
runing test on did rotation
Christiantyemele Sep 30, 2024
de74e95
feat(): removing previous did from keylist
Christiantyemele Oct 2, 2024
503015f
fix(): test on did rotation
Christiantyemele Oct 3, 2024
73df4e3
feat(polish): did rotation
Christiantyemele Oct 7, 2024
b0f9876
feat(): sucessivefully verified from prior jwt
Christiantyemele Oct 7, 2024
87b443e
Delete crates/plugins/mediator-coordination/src/forward/routing.rs
Christiantyemele Oct 7, 2024
dae5c83
Delete .vscode/launch.json
Christiantyemele Oct 7, 2024
56a338b
Delete crates/plugins/mediator-coordination/src/didcomm/bridge.rs
Christiantyemele Oct 7, 2024
9dc17d0
Merge remote-tracking branch 'origin/main' into did-rotation-handler
Christiantyemele Oct 7, 2024
7ac8bac
feat(rotation): polish test did rotation
Christiantyemele Oct 8, 2024
5bc1a71
feat(polish): did rotation test fix and polished
Christiantyemele Oct 10, 2024
d1c4294
Merge branch 'main' into did-rotation-handler
Christiantyemele Oct 10, 2024
186cb40
fix(test): fixed failing tests in midlw
Christiantyemele Oct 10, 2024
6475a8a
Merge branch 'did-rotation-handler' of https://github.com/adorsys/did…
Christiantyemele Oct 10, 2024
c9fb0a8
fix(): code review
Christiantyemele Oct 22, 2024
bbd38f8
fix(): errors refactor
Christiantyemele Oct 22, 2024
67b3dd1
feat(): added test to assert for did rotation
Christiantyemele Oct 25, 2024
787f2b2
fix(): small refactor on tests
Christiantyemele Oct 25, 2024
579c066
fix(): code review and refactor
Christiantyemele Nov 4, 2024
41bde42
feat(): merge main into did-rotation-handler
Christiantyemele Nov 4, 2024
6f47940
fix(): code formatting
Christiantyemele Nov 4, 2024
9ded401
feat(): code review and refactoring
Christiantyemele Nov 4, 2024
07e51f4
fix(): typos fix
Christiantyemele Nov 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ hex = "0.4.3"
subtle = "2.5.0"
regex = "1.10.2"
mongodb = "2.7.1"
tower = "0.4"
nix = "0.22.0"
uuid = "1.4.1"
axum = "0.6.20"
Expand Down Expand Up @@ -89,6 +88,7 @@ did-endpoint = { workspace = true, optional = true }
oob-messages = { workspace = true, optional = true }



[features]
default = ["plugin-index", "plugin-did_endpoint", "plugin-oob_messages"]

Expand Down
1 change: 1 addition & 0 deletions crates/web-plugins/didcomm-messaging/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ uuid = { workspace = true, features = ["v4"] }
hyper = { workspace = true, features = ["full"] }
lazy_static.workspace = true


[dev-dependencies]
dotenv-flow = "0.15.0"
hyper = "0.14.27"
Expand Down
348 changes: 348 additions & 0 deletions crates/web-plugins/didcomm-messaging/src/did_rotation/did_rotation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
use super::errors::RotationError;
use crate::{didcomm::bridge::LocalDIDResolver, model::stateful::entity::Connection};
use axum::response::{IntoResponse, Response};
use database::{Repository, RepositoryError};
use didcomm::{FromPrior, Message};
use mongodb::bson::doc;
use std::sync::Arc;

/// https://identity.foundation/didcomm-messaging/spec/#did-rotation
pub async fn did_rotation(
msg: Message,
connection_repos: &Arc<dyn Repository<Connection>>,
) -> Result<(), Response> {
// Check if from_prior is none
if msg.from_prior.is_none() {
return Ok(());
}
let jwt = msg.from_prior.unwrap();
let did_resolver = LocalDIDResolver::default();

// decode and validate jwt signature
let (from_prior, _kid) = FromPrior::unpack(&jwt, &did_resolver)
.await
.map_err(|_| RotationError::InvalidFromPrior.json().into_response())?;
let prev = from_prior.iss;

// validate if did is known
let _ = match connection_repos
.find_one_by(doc! {"client_did": &prev})
.await
.unwrap()
{
Some(mut connection) => {
// get new did for communication, if empty then we end the relationship
let new = from_prior.sub;

if new.is_empty() {
let id = connection.id.unwrap_or_default();
return connection_repos
.delete_one(id)
.await
.map_err(|_| RotationError::TargetNotFound.json().into_response());
}

let did_index = connection.keylist.iter().position(|did| did == &prev);

if did_index.is_some() {
connection.keylist.swap_remove(did_index.unwrap());

connection.keylist.push(new.clone());
} else {
// scenario in which there is rotation prior to keylist update
connection.keylist.push(new.clone());
}

// store updated connection
let _confirmations: Result<Connection, RepositoryError> = match connection_repos
.update(Connection {
client_did: new,
..connection
})
.await
{
Ok(conn) => Ok(conn),
Err(_) => return Err(RotationError::RepositoryError.json().into_response()),
};
}

None => {
return Err(RotationError::UnknownIssuer.json().into_response())?;
}
};
Ok(())
}

Blindspot22 marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(test)]
mod test {
use std::sync::Arc;

use did_utils::{didcore::Document, jwk::Jwk};
use didcomm::secrets::SecretsResolver;
use hyper::{header::CONTENT_TYPE, Body, Method, Request, StatusCode};
use mongodb::bson::doc;
use tower::ServiceExt;

use crate::{
constant::DIDCOMM_ENCRYPTED_MIME_TYPE,
didcomm::bridge::LocalSecretsResolver,
repository::stateful::tests::{
MockConnectionRepository, MockMessagesRepository, MockSecretsRepository,
},
util::{self, MockFileSystem},
web::{self, AppState, AppStateRepository},
};

pub fn new_secrets_resolver() -> impl SecretsResolver {
let secret_id = "did:key:z6MkqvgpxveKbuygKXnoRcD3jtLTJLgv7g6asLGLsoC4sUEp#z6LSeQmJnBaXhHz81dCGNDeTUUdMcX1a8p5YSVacaZEDdscp";
let secret: Jwk = serde_json::from_str(
r#"{
"kty": "OKP",
"crv": "X25519",
"d": "EIR1SxQ67uhVaeUd__sJZ_9pLLgtbVTq12Km8FI5TWY",
"x": "KKBfakcXdzmJ3hhL0mVDg8OIwhTr9rPg_gvc-kPQpCU"
}"#,
)
.unwrap();
LocalSecretsResolver::new(&secret_id, &secret)
}

pub fn prev_did() -> String {
"did:key:z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM".to_string()
}
pub fn new_did() -> String {
"did:key:z6MkqvgpxveKbuygKXnoRcD3jtLTJLgv7g6asLGLsoC4sUEp".to_string()
}
pub fn setup() -> Arc<AppState> {
let public_domain = String::from("http://alice-mediator.com");

let mut mock_fs = MockFileSystem;
let keystore = util::read_keystore(&mut mock_fs, "").unwrap();
let diddoc = didoc();
let repository = AppStateRepository {
connection_repository: Arc::new(MockConnectionRepository::from(_initial_connections())),
secret_repository: Arc::new(MockSecretsRepository::from(vec![])),
message_repository: Arc::new(MockMessagesRepository::from(vec![])),
};

let state = Arc::new(AppState::from(
public_domain,
diddoc,
keystore,
Some(repository),
));

state
}
fn _initial_connections() -> Vec<Connection> {
let _recipient_did = prev_did();

let connections = format!(
r##"[
{{
"_id": {{
"$oid": "6580701fd2d92bb3cd291b2a"
}},

"client_did": "{_recipient_did}",
"mediator_did": "did:web:alice-mediator.com:alice_mediator_pub",
"routing_did": "did:key:generated",
"keylist": [
"{_recipient_did}"
]
}}
]"##
);

serde_json::from_str(&connections).unwrap()
}

use didcomm::{FromPrior, Message};
use serde_json::json;
use uuid::Uuid;

use crate::{didcomm::bridge::LocalDIDResolver, model::stateful::entity::Connection};

use super::did_rotation;

fn didoc() -> Document {
let doc: did_utils::didcore::Document = serde_json::from_str(
r#"{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:alice-mediator.com:alice_mediator_pub",
"verificationMethod": [
{
"id": "did:web:alice-mediator.com:alice_mediator_pub#keys-1",
"type": "JsonWebKey2020",
"controller": "did:web:alice-mediator.com:alice_mediator_pub",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "Z0GqpN71rMcnAkky6_J6Bfknr8B-TBsekG3qdI0EQX4"
}
},
{
"id": "did:web:alice-mediator.com:alice_mediator_pub#keys-2",
"type": "JsonWebKey2020",
"controller": "did:web:alice-mediator.com:alice_mediator_pub",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "Z0GqpN71rMcnAkky6_J6Bfknr8B-TBsekG3qdI0EQX4"
}
},
{
"id": "did:web:alice-mediator.com:alice_mediator_pub#keys-3",
"type": "JsonWebKey2020",
"controller": "did:web:alice-mediator.com:alice_mediator_pub",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "SHSUZ6V3x355FqCzIUfgoPzrZB0BQs0JKyag4UfMqHQ"
}
}
],
"assertionMethod": [
"did:web:alice-mediator.com:alice_mediator_pub#keys-1"
],
"authentication": [
"did:web:alice-mediator.com:alice_mediator_pub#keys-1"
],
"keyAgreement": [
"did:web:alice-mediator.com:alice_mediator_pub#keys-3"
],
"service": []
}"#,
)
.unwrap();
doc
}

async fn test_jwt_data() -> String {
pub fn prev_secrets_resolver() -> impl SecretsResolver {
let secret_id = "did:key:z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM#z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM";
let secret: Jwk = serde_json::from_str(
r#"{
"kty": "OKP",
"crv": "Ed25519",
"x": "sZPvulKOXCES3D8Eya3LVnlgOpEaBohCqZ7emD8VXAA",
"d": "kUKFMD3RCZpk556fG0hx9GUrmdvb8t7k3TktPXCi4CY"
}"#,
)
.unwrap();

LocalSecretsResolver::new(&secret_id, &secret)
}

let from_prior = FromPrior {
iss: prev_did(),
sub: new_did(),
aud: None,
exp: None,
nbf: None,
iat: None,
jti: None,
};

let did_resolver = LocalDIDResolver::new(&didoc());
let kid = "did:key:z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM#z6MkrQT3VKYGkbPaYuJeBv31gNgpmVtRWP5yTocLDBgPpayM";
let (jwt, _kid) = from_prior
.pack(Some(&kid), &did_resolver, &prev_secrets_resolver())
.await
.unwrap();
jwt
}

fn test_message_payload(jwt: String) -> Message {
let msg = Message::build(
Uuid::new_v4().to_string(),
"https://didcomm.org/coordinate-mediation/2.0/keylist-update".to_owned(),
json!({"updates": [
{
"recipient_did": "did:key:z6MkfyTREjTxQ8hUwSwBPeDHf3uPL3qCjSSuNPwsyMpWUGH7",
"action": "add"
},
{
"recipient_did": "did:key:alice_identity_pub2@alice_mediator",
"action": "remove"
}
]}),
)
.header("return_route".into(), json!("all"))
.to("did:web:alice-mediator.com:alice_mediator_pub".to_string())
.from(new_did())
.from_prior(jwt)
.finalize();
msg
}

#[tokio::test]
async fn unit_test_on_did_rotation() {
let jwt = test_jwt_data().await;
let state = setup();
let AppStateRepository {
connection_repository,
..
} = state.repository.as_ref().unwrap();

let msg = test_message_payload(jwt);
did_rotation(msg, &connection_repository).await.unwrap();

// assert if did was rotated on mediator's site
let _ = match connection_repository
.find_one_by(doc! {"client_did": new_did()})
.await
.unwrap()
{
Some(conn) => {
assert_eq!(conn.client_did, new_did())
}
None => {
panic!("Rotation Error")
}
};
}

#[tokio::test]
async fn test_integrate_with_unified_route() {
let did_resolver = LocalDIDResolver::new(&didoc());
let jwt = test_jwt_data().await;
let msg = test_message_payload(jwt);
let (msg, _) = msg
.pack_encrypted(
"did:web:alice-mediator.com:alice_mediator_pub",
Some(&new_did()),
None,
&did_resolver,
&new_secrets_resolver(),
&didcomm::PackEncryptedOptions::default(),
)
.await
.unwrap();

// Send request
let app = web::routes(Arc::clone(&setup()));

let response = app
.oneshot(
Request::builder()
.uri(String::from("/"))
.method(Method::POST)
.header(CONTENT_TYPE, DIDCOMM_ENCRYPTED_MIME_TYPE)
.body(Body::from(msg))
.unwrap(),
)
.await
.unwrap();

// Assert response's metadata
assert_eq!(response.status(), StatusCode::ACCEPTED);
assert_eq!(
response.headers().get(CONTENT_TYPE).unwrap(),
DIDCOMM_ENCRYPTED_MIME_TYPE
);
}
}
Loading
Loading