[java-idp-plugin-vci] 01/01: Percent encode the credential offer

Codeberg noreply at shibboleth.net
Mon Sep 21 10:30:30 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch bugs/LowHanging
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/0d2799d243c752fe912d01ecb4351abddff9933e

commit 0d2799d243c752fe912d01ecb4351abddff9933e
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Mon Sep 21 13:29:54 2026 +0300

    Percent encode the credential offer
---
 README.md                                                    |  9 +++++----
 .../messaging/impl/CredentialOfferSuccessResponse.java       | 12 +++++++++++-
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 0c7477b..0a7d6dd 100644
--- a/README.md
+++ b/README.md
@@ -604,11 +604,12 @@ curl -u 'offer-caller:CHANGE-ME' \
 }'
 ```
 
-The response is the Credential Offer as a deep link, in one line and with no whitespace. This
-is what you put in the QR code. The `value` of the transaction code is not in it:
+The response is the Credential Offer as a deep link, in one line and with no whitespace. The
+offer JSON travels as a query parameter, so it is percent-encoded. This is what you put in
+the QR code. The `value` of the transaction code is not in it:
 
 ```
-openid-credential-offer://?credential_offer={"credential_issuer":"https://issuer.example.org","credential_configuration_ids":["GeantIncubatorDiploma_SDJWT","GeantIncubatorDiploma_W3C"],"grants":{"urn:ietf:params:oauth:grant-type:pre-authorized_code":{"pre-authorized_code":"AAdzZWNyZXQxWKPQ...","tx_code":{"input_mode":"numeric","length":6,"description":"The code we sent to your phone"}}}}
+openid-credential-offer://?credential_offer=%7B%22credential_issuer%22:%22https://issuer.example.org%22,%22credential_configuration_ids%22:%5B%22GeantIncubatorDiploma_SDJWT%22,%22GeantIncubatorDiploma_W3C%22%5D,%22grants%22:%7B%22urn:ietf:params:oauth:grant-type:pre-authorized_code%22:%7B%22pre-authorized_code%22:%22AAdzZWNyZXQxWKPQ...%22,%22tx_code%22:%7B%22input_mode%22:%22numeric%22,%22length%22:6,%22description%22:%22The%20code%20we%20sent%20to%20your%20phone%22%7D%7D%7D%7D
 ```
 
 </details>
@@ -639,7 +640,7 @@ Nothing in the offer is per user here, so you write it by hand. The same url ser
 and you may print it once as a QR code:
 
 ```
-openid-credential-offer://?credential_offer={"credential_issuer":"https://issuer.example.org","credential_configuration_ids":["GeantIncubatorDiploma_SDJWT"],"grants":{"authorization_code":{}}}
+openid-credential-offer://?credential_offer=%7B%22credential_issuer%22:%22https://issuer.example.org%22,%22credential_configuration_ids%22:%5B%22GeantIncubatorDiploma_SDJWT%22%5D,%22grants%22:%7B%22authorization_code%22:%7B%7D%7D%7D
 ```
 
 The `authorization_code` grant is what tells the wallet to run this flow. What is issued comes
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferSuccessResponse.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferSuccessResponse.java
index 6e11489..78cee42 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferSuccessResponse.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/messaging/impl/CredentialOfferSuccessResponse.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.net.PercentEscaper;
 import com.nimbusds.common.contenttype.ContentType;
 import com.nimbusds.oauth2.sdk.SuccessResponse;
 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
@@ -46,6 +47,14 @@ public class CredentialOfferSuccessResponse implements SuccessResponse {
     @Nonnull
     private Logger log = LoggerFactory.getLogger(CredentialOfferSuccessResponse.class);
 
+    /**
+     * Percent-encoder for the Credential Offer query parameter value.
+     * 
+     * RFC 3986 unreserved set plus ":", "/" and ",".
+     */
+    @Nonnull
+    private static final PercentEscaper OFFER_ESCAPER = new PercentEscaper("-._~:/,", false);
+
     /** credential_issuer parameter. */
     @JsonProperty("credential_issuer")
     private final String credentialIssuer;
@@ -116,7 +125,8 @@ public class CredentialOfferSuccessResponse implements SuccessResponse {
      */
     private String toOffer() throws JsonProcessingException {
         final ObjectMapper objectMapper = new ObjectMapper();
-        return "openid-credential-offer://?credential_offer=" + objectMapper.writeValueAsString(this);
+        return "openid-credential-offer://?credential_offer="
+                + OFFER_ESCAPER.escape(objectMapper.writeValueAsString(this));
     }
 
     /** {@inheritDoc} */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list