diff --git a/CHANGELOG.md b/CHANGELOG.md index d579de07..23485a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### 11-04-2024 +`/v1/offers` incorrectly returned with Content-Type `application/json`. The Content-Type has now been changed to `application/x-www-form-urlencoded`. + ### 24-01-2024 Environment variable `AGENT_APPLICATION_HOST` has changed to `AGENT_APPLICATION_URL` and requires the complete URL. e.g.: diff --git a/Cargo.lock b/Cargo.lock index d423a37f..c1e652a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,8 +124,8 @@ dependencies = [ "anyhow", "async-trait", "cqrs-es", - "openssl", "reqwest 0.12.2", + "rustls 0.23.4", "serde", "serde_json", "serde_with 3.7.0", @@ -5582,6 +5582,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" +dependencies = [ + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index 3a439c66..b9cdb649 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,15 +18,10 @@ rust-version = "1.76.0" [workspace.dependencies] did_manager = { git = "https://git@github.com/impierce/did-manager.git", rev = "54959ac" } -# did_manager = { path = "../did-manager" } siopv2 = { git = "https://git@github.com/impierce/openid4vc.git", rev = "13f24f5" } -# siopv2 = { path = "../openid4vc/siopv2" } oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", rev = "13f24f5" } -# oid4vci = { path = "../openid4vc/oid4vci" } oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", rev = "13f24f5" } -# oid4vc-core = { path = "../openid4vc/oid4vc-core" } oid4vc-manager = { git = "https://git@github.com/impierce/openid4vc.git", rev = "13f24f5" } -# oid4vc-manager = { path = "../openid4vc/oid4vc-manager" } async-trait = "0.1" axum = { version = "0.7", features = ["tracing"] } diff --git a/agent_api_rest/openapi.yaml b/agent_api_rest/openapi.yaml index 22d0a79a..f183ecce 100644 --- a/agent_api_rest/openapi.yaml +++ b/agent_api_rest/openapi.yaml @@ -111,7 +111,7 @@ paths: "200": description: Offer created successfully. Response value should be displayed to the user in the form of a QR code. content: - text/plain: + application/x-www-form-urlencoded: schema: type: string example: openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fcredential-issuer.example.com%2F%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22type%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%7D%5D%7D @@ -168,10 +168,10 @@ paths: example: "/request/43482b98aa2e071231082fc29db7a59f342a643b0c590f71083af3c7ae83f3c3" description: URL of the created resource content: - text/plain: + application/x-www-form-urlencoded: schema: type: string - example: "openid://?client_id=did%3Akey%3Az6MkiieyoLMSVsJAZv7Jje5wWSkDEymUgkyF8kbcrjZpX3qd&request_uri=http%3A%2F%2F192.168.1.127%3A3033%2Frequest%2F43482b98aa2e071231082fc29db7a59f342a643b0c590f71083af3c7ae83f3c3" + example: "siopv2://idtoken?client_id=did%3Akey%3Az6MkiieyoLMSVsJAZv7Jje5wWSkDEymUgkyF8kbcrjZpX3qd&request_uri=http%3A%2F%2F192.168.1.127%3A3033%2Frequest%2F43482b98aa2e071231082fc29db7a59f342a643b0c590f71083af3c7ae83f3c3" /v1/authorization_requests/{authorization_requests_id}: get: summary: Get the Authorization Request with the given Authorization Request ID diff --git a/agent_api_rest/postman/ssi-agent.postman_collection.json b/agent_api_rest/postman/ssi-agent.postman_collection.json index bf3ee024..e24ccf12 100644 --- a/agent_api_rest/postman/ssi-agent.postman_collection.json +++ b/agent_api_rest/postman/ssi-agent.postman_collection.json @@ -66,19 +66,16 @@ "// Split the string on the first '=' character and take the second item", "const [, secondItem] = decodedString.split('=', 2);", "", - "// Remove the last character from secondItem", - "const secondItemWithoutLastChar = secondItem.slice(0, -1);", - "", - "const { grants } = JSON.parse(secondItemWithoutLastChar);", - "", - "const pre_authorized_code = grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'];", + "var jsonObject = JSON.parse(secondItem);", + "const pre_authorized_code = jsonObject.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code']['pre-authorized_code'];", "", "if(pre_authorized_code){", " pm.collectionVariables.set(\"PRE_AUTHORIZED_CODE\",pre_authorized_code)", "}", "" ], - "type": "text/javascript" + "type": "text/javascript", + "packages": {} } } ], diff --git a/agent_api_rest/src/issuance/credential_issuer/credential.rs b/agent_api_rest/src/issuance/credential_issuer/credential.rs index a296063c..fbdb45c3 100644 --- a/agent_api_rest/src/issuance/credential_issuer/credential.rs +++ b/agent_api_rest/src/issuance/credential_issuer/credential.rs @@ -152,6 +152,7 @@ mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let body: Value = serde_json::from_slice(&body).unwrap(); diff --git a/agent_api_rest/src/issuance/credential_issuer/token.rs b/agent_api_rest/src/issuance/credential_issuer/token.rs index 09d7184c..74453701 100644 --- a/agent_api_rest/src/issuance/credential_issuer/token.rs +++ b/agent_api_rest/src/issuance/credential_issuer/token.rs @@ -91,6 +91,7 @@ pub mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let token_response: TokenResponse = serde_json::from_slice(&body).unwrap(); diff --git a/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs b/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs index d5b8d600..09bcac67 100644 --- a/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs +++ b/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs @@ -51,6 +51,7 @@ mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let authorization_server_metadata: AuthorizationServerMetadata = serde_json::from_slice(&body).unwrap(); diff --git a/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs b/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs index 1e2a904c..8e5c0c90 100644 --- a/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs +++ b/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs @@ -61,6 +61,7 @@ mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let credential_issuer_metadata: CredentialIssuerMetadata = serde_json::from_slice(&body).unwrap(); diff --git a/agent_api_rest/src/issuance/credentials.rs b/agent_api_rest/src/issuance/credentials.rs index dd720c82..dc1d782d 100644 --- a/agent_api_rest/src/issuance/credentials.rs +++ b/agent_api_rest/src/issuance/credentials.rs @@ -160,6 +160,7 @@ pub mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::CREATED); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let get_credentials_endpoint = response .headers() @@ -186,6 +187,7 @@ pub mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let body: Value = serde_json::from_slice(&body).unwrap(); diff --git a/agent_api_rest/src/issuance/offers.rs b/agent_api_rest/src/issuance/offers.rs index 78dc22ba..2402e294 100644 --- a/agent_api_rest/src/issuance/offers.rs +++ b/agent_api_rest/src/issuance/offers.rs @@ -9,6 +9,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; +use hyper::header; use serde_json::Value; use tracing::info; @@ -46,7 +47,12 @@ pub(crate) async fn offers(State(state): State, Json(payload): Js Ok(Some(OfferView { form_url_encoded_credential_offer, .. - })) => (StatusCode::OK, Json(form_url_encoded_credential_offer)).into_response(), + })) => ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/x-www-form-urlencoded")], + form_url_encoded_credential_offer, + ) + .into_response(), _ => StatusCode::INTERNAL_SERVER_ERROR.into_response(), } } @@ -93,31 +99,34 @@ pub mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response.headers().get("Content-Type").unwrap(), + "application/x-www-form-urlencoded" + ); let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); - - let value: Value = serde_json::from_slice(&body).unwrap(); - let pre_authorized_code = if let CredentialOffer::CredentialOffer(credential_offer) = - CredentialOffer::from_str(value.as_str().unwrap()).unwrap() - { - let CredentialOfferParameters { - grants: - Some(Grants { - pre_authorized_code: - Some(PreAuthorizedCode { - pre_authorized_code, .. - }), - .. - }), - .. - } = *credential_offer - else { + let body: String = String::from_utf8(body.to_vec()).unwrap(); + + let pre_authorized_code = + if let CredentialOffer::CredentialOffer(credential_offer) = CredentialOffer::from_str(&body).unwrap() { + let CredentialOfferParameters { + grants: + Some(Grants { + pre_authorized_code: + Some(PreAuthorizedCode { + pre_authorized_code, .. + }), + .. + }), + .. + } = *credential_offer + else { + unreachable!() + }; + pre_authorized_code + } else { unreachable!() }; - pre_authorized_code - } else { - unreachable!() - }; pre_authorized_code } diff --git a/agent_api_rest/src/verification/authorization_requests.rs b/agent_api_rest/src/verification/authorization_requests.rs index 9b35df38..7792ced1 100644 --- a/agent_api_rest/src/verification/authorization_requests.rs +++ b/agent_api_rest/src/verification/authorization_requests.rs @@ -79,7 +79,10 @@ pub(crate) async fn authorization_requests( .. })) => ( StatusCode::CREATED, - [(header::LOCATION, &format!("/v1/authorization_requests/{state}"))], + [ + (header::LOCATION, format!("/v1/authorization_requests/{state}").as_str()), + (header::CONTENT_TYPE, "application/x-www-form-urlencoded"), + ], form_url_encoded_authorization_request, ) .into_response(), @@ -120,6 +123,10 @@ pub mod tests { .unwrap(); assert_eq!(response.status(), StatusCode::CREATED); + assert_eq!( + response.headers().get("Content-Type").unwrap(), + "application/x-www-form-urlencoded" + ); let get_request_endpoint = response .headers() diff --git a/agent_api_rest/src/verification/relying_party/request.rs b/agent_api_rest/src/verification/relying_party/request.rs index 397a2e1f..a78cd03b 100644 --- a/agent_api_rest/src/verification/relying_party/request.rs +++ b/agent_api_rest/src/verification/relying_party/request.rs @@ -5,20 +5,23 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; +use hyper::header; +/// Instead of directly embedding the Authorization Request into a QR-code or deeplink, the `Relying Party` can embed a +/// `request_uri` that points to this endpoint from where the Authorization Request Object can be retrieved. +/// As described here: https://www.rfc-editor.org/rfc/rfc9101.html#name-passing-a-request-object-by- #[axum_macros::debug_handler] pub(crate) async fn request( State(verification_state): State, Path(request_id): Path, ) -> Response { - // Return the authorization request object. match query_handler(&request_id, &verification_state.query.authorization_request).await { Ok(Some(AuthorizationRequestView { signed_authorization_request_object: Some(signed_authorization_request_object), .. })) => ( StatusCode::OK, - // TODO: set the content type to `application/jwt` also check if this is necessary for other endpoints + [(header::CONTENT_TYPE, "application/jwt")], signed_authorization_request_object, ) .into_response(), @@ -54,6 +57,8 @@ pub mod tests { assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.headers().get("Content-Type").unwrap(), "application/jwt"); + let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.unwrap(); let body: String = String::from_utf8(body.to_vec()).unwrap(); diff --git a/agent_application/docker/README.md b/agent_application/docker/README.md index d42c139f..dadb217c 100644 --- a/agent_application/docker/README.md +++ b/agent_application/docker/README.md @@ -16,24 +16,26 @@ Inside the folder `/agent_application/docker`: 2. Optionally, add the following environment variables: - `AGENT_ISSUANCE_CREDENTIAL_NAME`: To set the name of the credentials that will be issued. - `AGENT_ISSUANCE_CREDENTIAL_LOGO_URL`: To set the URL of the logo that will be used in the credentials. -3. By default, UniCore will automatically generate a temporary secure Stronghold file which will be used to sign authorization - requests and credentials. Note that using this default option, this Stronghold file will NOT be persisted. If you - want to ensure that the key material that is used for signing data will always be consistent, you will need to supply - an existing Stronghold file. This can be done by mounting the Stronghold file in the - `docker-compose.yml` file. Example: - ```yaml - volumes: - - /path/to/stronghold:/app/res/stronghold - ``` - You will also need to set the following environment variables: - - `AGENT_SECRET_MANAGER_STRONGHOLD_PATH`: The path to the Stronghold file. This value must correspond to the path to which - the Stronghold is mounted. Set to `/app/res/stronghold` by default. It - is recommended to not change this environment variable. - - `AGENT_SECRET_MANAGER_STRONGHOLD_PASSWORD`: To set the password - - `AGENT_SECRET_MANAGER_ISSUER_KEY_ID`: To set the key id -1. Optionally it is possible to configure an HTTP Event Publisher that can listen to certain events in `UniCore` +> [!IMPORTANT] +> 3. By default, UniCore currently uses a default Stronghold file which is used for storing secrets. Using this default +> Stronghold is for testing purposes only and should not be used in production. To use your own Stronghold file, you +> need to mount it in the `docker-compose.yml` file by replacing the default volume. Example: +> ```yaml +> volumes: +> # - ../../agent_secret_manager/tests/res/test.stronghold:/app/res/stronghold # Default Stronghold file +> - /path/to/stronghold:/app/res/stronghold +> ``` +> It is recommended to not change the target path `/app/res/stronghold`. +> +> You will also need to set the following environment variables: +> - `AGENT_SECRET_MANAGER_STRONGHOLD_PATH`: The path to the Stronghold file. This value must correspond to the path to which +> the Stronghold is mounted. Set to `/app/res/stronghold` by default. It +> is recommended to not change this environment variable. +> - `AGENT_SECRET_MANAGER_STRONGHOLD_PASSWORD`: To set the password +> - `AGENT_SECRET_MANAGER_ISSUER_KEY_ID`: To set the key id +4. Optionally it is possible to configure an HTTP Event Publisher that can listen to certain events in `UniCore` and publish them to a `target_url`. More information about the HTTP Event Publisher can be found [here](../../agent_event_publisher_http/README.md). -2. To start the **SSI Agent**, a **Postgres** database along with **pgadmin** (Postgres Admin Interface) simply run: +5. To start the **SSI Agent**, a **Postgres** database along with **pgadmin** (Postgres Admin Interface) simply run: ```bash docker compose up diff --git a/agent_event_publisher_http/Cargo.toml b/agent_event_publisher_http/Cargo.toml index 6825b723..af4aa7d4 100644 --- a/agent_event_publisher_http/Cargo.toml +++ b/agent_event_publisher_http/Cargo.toml @@ -12,7 +12,11 @@ agent_verification = { path = "../agent_verification" } anyhow = "1.0" async-trait.workspace = true cqrs-es.workspace = true -openssl = { version = "0.10", features = ["vendored"] } +rustls = { version = "0.23", default-features = false, features = [ + "logging", + "std", + "tls12" +] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde.workspace = true serde_with.workspace = true diff --git a/agent_issuance/src/server_config/queries.rs b/agent_issuance/src/server_config/queries.rs index ba90a24a..fe7bef42 100644 --- a/agent_issuance/src/server_config/queries.rs +++ b/agent_issuance/src/server_config/queries.rs @@ -30,8 +30,10 @@ impl View for ServerConfigView { } => { self.credential_issuer_metadata .as_mut() - .unwrap() - .credential_configurations_supported = credential_configurations_supported.clone() + .map(|credential_issuer_metadata| { + credential_issuer_metadata.credential_configurations_supported = + credential_configurations_supported.clone() + }); } } }