[java-idp-plugin-vci] 01/07: Finally verify the key proof signature
Codeberg
noreply at shibboleth.net
Thu Sep 24 13:16:26 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/ac862ea3765aec763f83b6c46174e5bcc386b353
commit ac862ea3765aec763f83b6c46174e5bcc386b353
Author: Janne Lauros <janne.lauros at csc.fi>
AuthorDate: Thu Sep 24 16:04:00 2026 +0300
Finally verify the key proof signature
---
.../impl/ExtendedJOSEObjectCredentialResolver.java | 2 +-
.../security/impl/TokenKeyTrustEngine.java | 19 +++-
.../security/impl/TokenKeyTrustEngineTest.java | 116 +++++++++++++++++++++
3 files changed, 131 insertions(+), 6 deletions(-)
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/credential/impl/ExtendedJOSEObjectCredentialResolver.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/credential/impl/ExtendedJOSEObjectCredentialResolver.java
index ca3c090..dc3b2f0 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/credential/impl/ExtendedJOSEObjectCredentialResolver.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/credential/impl/ExtendedJOSEObjectCredentialResolver.java
@@ -88,7 +88,7 @@ public class ExtendedJOSEObjectCredentialResolver extends BasicJOSEObjectCredent
try {
final JWK didJwk = JWK
.parse(new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8));
- final Credential cred = buildJWKCredential(didJwk, kid);
+ final Credential cred = buildJWKCredential(didJwk, didJwk.getKeyID());
if (cred != null) {
credentials.add(cred);
}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngine.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngine.java
index e1ca4e3..9476d4c 100644
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngine.java
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngine.java
@@ -36,10 +36,10 @@ import net.shibboleth.shared.resolver.CriteriaSet;
/**
* A {@link TrustEngine} implementation for Signed JSON Web Tokens. The token
* derived public key is used as the trust basis.
- *
- * Note, trust engine does not validate the jwk in any way but lets the wired
- * resolver to do the work.
- *
+ *
+ * <p>The signature must verify with a key the token itself carries, the wired resolver deciding
+ * which keys those are. Whether such a key is one to trust is established outside this engine, by
+ * the key attestation of a key proof.</p>
*/
public class TokenKeyTrustEngine extends BaseSignedJWTTrustEngine<Credential> {
@@ -62,7 +62,16 @@ public class TokenKeyTrustEngine extends BaseSignedJWTTrustEngine<Credential> {
@Override
protected boolean doValidate(@Nonnull final SignedJWT signedJWT, @Nonnull final CriteriaSet trustBasisCriteria)
throws SecurityException {
- return true;
+
+ for (final Credential credential : resolveTokenCredentials(signedJWT)) {
+ assert credential != null;
+ if (verifySignature(signedJWT, credential)) {
+ return true;
+ }
+ }
+
+ log.warn("Signature of the token is verified by none of the keys the token carries");
+ return false;
}
/** {@inheritDoc} */
diff --git a/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngineTest.java b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngineTest.java
new file mode 100644
index 0000000..59fd739
--- /dev/null
+++ b/openid-vci-impl/src/test/java/org/geant/shibboleth/plugin/openidvci/security/impl/TokenKeyTrustEngineTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2025, GÉANT
+ *
+ * Licensed under the Apache License, Version 2.0 (the “License”); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an “AS IS” BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.geant.shibboleth.plugin.openidvci.security.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import org.geant.shibboleth.plugin.openidvci.security.credential.impl.ExtendedJOSEObjectCredentialResolver;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.security.jose.SignatureValidationParameters;
+import net.shibboleth.oidc.security.jose.criterion.SignatureValidationParametersCriterion;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/** Unit tests for {@link TokenKeyTrustEngine}. */
+public class TokenKeyTrustEngineTest {
+
+ private TokenKeyTrustEngine engine;
+
+ private ECKey key;
+
+ private CriteriaSet criteria;
+
+ @BeforeMethod
+ protected void setUp() throws Exception {
+ engine = new TokenKeyTrustEngine(new ExtendedJOSEObjectCredentialResolver());
+ key = new ECKeyGenerator(Curve.P_256).algorithm(JWSAlgorithm.ES256).keyID("holder").generate();
+ criteria = new CriteriaSet(new SignatureValidationParametersCriterion(new SignatureValidationParameters()));
+ }
+
+ private SignedJWT proofWithKeyInHeader() throws Exception {
+ final SignedJWT proof = new SignedJWT(
+ new JWSHeader.Builder(JWSAlgorithm.ES256).type(new JOSEObjectType("openid4vci-proof+jwt"))
+ .jwk(key.toPublicJWK()).build(),
+ new JWTClaimsSet.Builder().issuer("client").audience("https://issuer.example.org").build());
+ proof.sign(new ECDSASigner(key));
+
+ return proof;
+ }
+
+ private SignedJWT proofWithKeyInDidJwkKid() throws Exception {
+ final String did = "did:jwk:" + Base64.getUrlEncoder().withoutPadding()
+ .encodeToString(key.toPublicJWK().toJSONString().getBytes(StandardCharsets.UTF_8)) + "#0";
+ final SignedJWT proof = new SignedJWT(
+ new JWSHeader.Builder(JWSAlgorithm.ES256).type(new JOSEObjectType("openid4vci-proof+jwt"))
+ .keyID(did).build(),
+ new JWTClaimsSet.Builder().issuer("client").audience("https://issuer.example.org").build());
+ proof.sign(new ECDSASigner(key));
+
+ return proof;
+ }
+
+ private SignedJWT withSignatureOf(final SignedJWT proof, final SignedJWT other) throws Exception {
+ final String[] parts = proof.serialize().split("\\.");
+
+ return SignedJWT.parse(parts[0] + "." + parts[1] + "." + other.serialize().split("\\.")[2]);
+ }
+
+ @Test
+ public void testSignatureOfTheKeyInTheHeader() throws Exception {
+ Assert.assertTrue(engine.validate(proofWithKeyInHeader(), criteria));
+ }
+
+ @Test
+ public void testSignatureOfTheKeyTheDidJwkKidNames() throws Exception {
+ Assert.assertTrue(engine.validate(proofWithKeyInDidJwkKid(), criteria));
+ }
+
+ @Test
+ public void testTamperedSignatureIsRefused() throws Exception {
+ final SignedJWT signedByAnotherKey = new SignedJWT(
+ new JWSHeader.Builder(JWSAlgorithm.ES256).build(),
+ new JWTClaimsSet.Builder().issuer("someone else").build());
+ signedByAnotherKey.sign(new ECDSASigner(new ECKeyGenerator(Curve.P_256).generate()));
+
+ Assert.assertFalse(
+ engine.validate(withSignatureOf(proofWithKeyInHeader(), signedByAnotherKey), criteria));
+ }
+
+ @Test
+ public void testProofCarryingNoKeyIsRefused() throws Exception {
+ final SignedJWT proof = new SignedJWT(
+ new JWSHeader.Builder(JWSAlgorithm.ES256).type(new JOSEObjectType("openid4vci-proof+jwt")).build(),
+ new JWTClaimsSet.Builder().issuer("client").build());
+ proof.sign(new ECDSASigner(key));
+
+ Assert.assertFalse(engine.validate(proof, criteria));
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list