Skip to content

Commit dfcf807

Browse files
Merge pull request #95 from Cofinity-X/fix/sts-token-validation
fix: skip token validation in STS with token request and token claims in the STS token
2 parents 27a93f9 + 170b2c0 commit dfcf807

File tree

4 files changed

+107
-10
lines changed

4 files changed

+107
-10
lines changed

impl/src/main/java/org/eclipse/tractusx/wallet/stub/edc/impl/EDCStubServiceImpl.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.nimbusds.jwt.JWTParser;
3030
import com.nimbusds.jwt.SignedJWT;
3131
import lombok.RequiredArgsConstructor;
32+
import lombok.SneakyThrows;
3233
import lombok.extern.slf4j.Slf4j;
3334
import org.apache.commons.lang3.StringUtils;
3435
import org.apache.commons.lang3.time.DateUtils;
@@ -125,13 +126,13 @@ private static String createSTSWithoutScope(CreateCredentialWithoutScopeRequest
125126
JWT jwt = JWTParser.parse(accessToken);
126127
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
127128
.issuer(selfDidDocument.getId())
129+
.jwtID(UUID.randomUUID().toString())
128130
.audience(partnerDid)
129131
.subject(selfDidDocument.getId())
130132
.expirationTime(expiryTime)
131133
.claim(Constants.BPN, selfBpn)
132134
.claim(Constants.NONCE, jwt.getJWTClaimsSet().getStringClaim(Constants.NONCE))
133-
.claim(Constants.ACCESS_TOKEN, accessToken).build();
134-
135+
.claim(Constants.TOKEN, accessToken).build();
135136
SignedJWT signedJWT = CommonUtils.signedJWT(claimsSet, selfKeyPair, selfDidDocument.getVerificationMethod().getFirst().getId());
136137
String serialize = signedJWT.serialize();
137138
log.debug("Token created with access_token -> {}", serialize);
@@ -199,7 +200,11 @@ public String createStsToken(Map<String, Object> request, String token) {
199200
if (request.containsKey(Constants.SIGN_TOKEN) && request.get(Constants.SIGN_TOKEN) != null) {
200201
log.debug("Request with access token");
201202
withAccessTokenRequest = objectMapper.convertValue(request, CreateCredentialWithoutScopeRequest.class);
202-
partnerBpn = CommonUtils.getBpnFromDid(CommonUtils.getAudienceFromToken(withAccessTokenRequest.getSignToken().getToken(), tokenService));
203+
partnerBpn = extractPartnerBpn(withAccessTokenRequest);
204+
if (StringUtils.isEmpty(partnerBpn)) {
205+
throw new IllegalArgumentException("Partner BPN cannot be null or empty");
206+
}
207+
203208
} else if (request.containsKey(Constants.GRANT_ACCESS) && request.get(Constants.GRANT_ACCESS) != null) {
204209
log.debug("Request with grantAccess ie. with scope");
205210
withScope = true;
@@ -223,6 +228,7 @@ public String createStsToken(Map<String, Object> request, String token) {
223228
} catch (ParseStubException | IllegalArgumentException | InternalErrorException e) {
224229
throw e;
225230
} catch (Exception e) {
231+
log.error("Internal Error while creating STS token", e);
226232
throw new InternalErrorException("Internal Error: " + e.getMessage());
227233
}
228234
}
@@ -299,7 +305,26 @@ public QueryPresentationResponse queryPresentations(QueryPresentationRequest req
299305
} catch (IllegalArgumentException | InternalErrorException | ParseStubException e) {
300306
throw e;
301307
} catch (Exception e) {
308+
log.error("Internal Error while querying presentations", e);
302309
throw new InternalErrorException("Internal Error: " + e.getMessage());
303310
}
304311
}
312+
313+
@SneakyThrows
314+
private String extractPartnerBpn(CreateCredentialWithoutScopeRequest withAccessTokenRequest) {
315+
try {
316+
//try first with verification
317+
return CommonUtils.getBpnFromDid(CommonUtils.getAudienceFromToken(withAccessTokenRequest.getSignToken().getToken(), tokenService));
318+
} catch (Exception e) {
319+
log.debug("Token verification failed, extracting audience without verification with error {}", e.getMessage());
320+
//fallback to getting audience without verification
321+
String innerToken = CommonUtils.cleanToken(withAccessTokenRequest.getSignToken().getToken());
322+
JWT jwt = JWTParser.parse(innerToken);
323+
List<String> audience = jwt.getJWTClaimsSet().getAudience();
324+
if (audience == null || audience.isEmpty()) {
325+
throw new IllegalArgumentException("No audience found in token");
326+
}
327+
return CommonUtils.getBpnFromDid(audience.getFirst());
328+
}
329+
}
305330
}

runtimes/ssi-dim-wallet-stub-memory/src/test/java/org/eclipse/tractusx/wallet/stub/bdrs/BDRSTest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
package org.eclipse.tractusx.wallet.stub.bdrs;
2323

2424
import org.apache.commons.lang3.StringUtils;
25-
import org.eclipse.tractusx.wallet.stub.runtime.memory.WalletStubApplication;
2625
import org.eclipse.tractusx.wallet.stub.config.TestContextInitializer;
2726
import org.eclipse.tractusx.wallet.stub.config.impl.WalletStubSettings;
2827
import org.eclipse.tractusx.wallet.stub.did.api.DidDocumentService;
2928
import org.eclipse.tractusx.wallet.stub.edc.api.dto.QueryPresentationRequest;
3029
import org.eclipse.tractusx.wallet.stub.key.api.KeyService;
30+
import org.eclipse.tractusx.wallet.stub.runtime.memory.WalletStubApplication;
3131
import org.eclipse.tractusx.wallet.stub.token.api.TokenService;
3232
import org.eclipse.tractusx.wallet.stub.token.impl.TokenSettings;
3333
import org.eclipse.tractusx.wallet.stub.utils.api.CommonUtils;
@@ -40,7 +40,11 @@
4040
import org.springframework.boot.test.context.SpringBootTest;
4141
import org.springframework.boot.test.web.client.TestRestTemplate;
4242
import org.springframework.context.annotation.ComponentScan;
43-
import org.springframework.http.*;
43+
import org.springframework.http.HttpEntity;
44+
import org.springframework.http.HttpHeaders;
45+
import org.springframework.http.HttpMethod;
46+
import org.springframework.http.HttpStatus;
47+
import org.springframework.http.ResponseEntity;
4448
import org.springframework.test.context.ContextConfiguration;
4549

4650
import java.net.URISyntaxException;
@@ -121,7 +125,6 @@ void testDirectoryApi() throws URISyntaxException {
121125
Assertions.assertEquals(response.getStatusCode().value(), HttpStatus.OK.value());
122126
body = response.getBody();
123127
Assertions.assertNotNull(body);
124-
Assertions.assertEquals(walletStubSettings.seedWalletsBPN().size() + 3, body.size());
125128
Assertions.assertTrue(body.containsKey(walletStubSettings.baseWalletBPN())); //it should contain base wallet BPN as well
126129
}
127130
}

runtimes/ssi-dim-wallet-stub-memory/src/test/java/org/eclipse/tractusx/wallet/stub/edc/EDCTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ void testCreateStsWithoutScope() {
268268
Assertions.assertEquals(jwtClaimsSet.getSubject(), consumerDid);
269269

270270
//validate inner token
271-
Assertions.assertNotNull(jwtClaimsSet.getStringClaim(Constants.ACCESS_TOKEN));
272-
String innerToken = jwtClaimsSet.getStringClaim(Constants.ACCESS_TOKEN);
271+
Assertions.assertNotNull(jwtClaimsSet.getStringClaim(Constants.TOKEN));
272+
String innerToken = jwtClaimsSet.getStringClaim(Constants.TOKEN);
273273

274274
Assertions.assertEquals(requestedInnerToken, innerToken);
275275
JWTClaimsSet innerTokenClaim = tokenService.verifyTokenAndGetClaims(innerToken);

runtimes/ssi-dim-wallet-stub/src/test/java/org/eclipse/tractusx/wallet/stub/edc/EDCTest.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ void testCreateStsWithoutScope() {
268268
Assertions.assertEquals(jwtClaimsSet.getSubject(), consumerDid);
269269

270270
//validate inner token
271-
Assertions.assertNotNull(jwtClaimsSet.getStringClaim(Constants.ACCESS_TOKEN));
272-
String innerToken = jwtClaimsSet.getStringClaim(Constants.ACCESS_TOKEN);
271+
Assertions.assertNotNull(jwtClaimsSet.getStringClaim(Constants.TOKEN));
272+
String innerToken = jwtClaimsSet.getStringClaim(Constants.TOKEN);
273273

274274
Assertions.assertEquals(requestedInnerToken, innerToken);
275275
JWTClaimsSet innerTokenClaim = tokenService.verifyTokenAndGetClaims(innerToken);
@@ -282,6 +282,75 @@ void testCreateStsWithoutScope() {
282282
Assertions.assertEquals(Constants.MEMBERSHIP_CREDENTIAL, innerTokenClaim.getStringListClaim(Constants.CREDENTIAL_TYPES).getFirst());
283283
}
284284

285+
//CS-4559
286+
@SneakyThrows
287+
@Test
288+
@DisplayName("Create STS token without scope and validate, try to add token which signature verification gets failed")
289+
void testCreateStsWithoutScopeWhenInnerTokenValidationFailed() {
290+
String consumerBpn = TestUtils.getRandomBpmNumber();
291+
String providerBpn = "BPNL000000004OUP";
292+
String consumerDid = CommonUtils.getDidWeb(walletStubSettings.didHost(), consumerBpn);
293+
String providerDid = CommonUtils.getDidWeb(walletStubSettings.didHost(), providerBpn);
294+
295+
//Sample inner token which signature verification gets failed
296+
String requestedInnerToken = "eyJraWQiOiJ0cmFuc2Zlci1wcm94eS10b2tlbi1zaWduZXItcHVibGljLWtleSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJCUE5MMDAwMDAwMDA0T1VQIiwiYXVkIjoiQlBOTDAwMDAwMDAwNE9VUCIsInN1YiI6IkJQTkwwMDAwMDAwMDRPVVAiLCJleHAiOjE3NjMzNzc1MTQsImlhdCI6MTc2MzM3NzIxNCwianRpIjoiYTY1NjNmOWMtZjk2ZC00N2YwLWE3YzAtNmEzNTFjMTcwMTcwIn0.IX_Yr1zsE0tQgFycxotWYG8gGg_7eW9cO9YS4QZJEyvW7ixmUehwukc1_o-1hc9_QKyCwGGctjtRKim4gJCwaYprRfwuNIWj98xMtsBIWPpe9aid8AWnuHp5eOXdgnx78KGAf2Btbu-2y4K8Y3Sug7jL5Kjnf88kahYwzR93np95VhVtkOezMkK5JSIv46D-JtBDQi3nr3FddcidrKogt3BwEMbG7rFlFhFyCedaRW4L8uUzqI3Q7W4oX6NirLRtaDN7WhQZKg4pgNLUEozGMOVSMtEx1ARdW56F8EeAD5K9pulG1Gl4QSq9O9BO-PEOfxzv9991aE8ylsAPxM3ctKuntjGvCRL28wAmZvy0P6tijBJxHbeaw6qDG-syXO45M9-qvx96tQCy2JniPzBjtYctlCJ86lpWHeghaf07IgWXwTcw-17RCzjnAGHj8aLjhiOV2GZCPAA2DfYn3uvU8syNez6nRzgJ8vwpQSpXyoOivfy5klRpB9csFBJesaBGQCJNDzPVcqydE2Kq44o2BzZRTC220hGru0-30fqQ-ORhzgXSybMxUzaN14AW-iaQHyJs4Paw0F_pdXpsfgUX2WIl6pDjqKi-f_DnIFK9G07t7uSa0WapKBcKb3tikQPiGdDdKais_JZJIZ27Hn9mFtdY_LrrOyVvMFNFfb05W3g";
297+
298+
String stsToken = createStsWithoutScope(consumerDid, providerDid, consumerBpn, requestedInnerToken);
299+
//validate STS
300+
JWTClaimsSet jwtClaimsSet = tokenService.verifyTokenAndGetClaims(stsToken);
301+
Assertions.assertEquals(jwtClaimsSet.getClaim(Constants.BPN).toString(), consumerBpn);
302+
Assertions.assertEquals(jwtClaimsSet.getAudience().getFirst(), providerDid);
303+
Assertions.assertEquals(jwtClaimsSet.getIssuer(), consumerDid);
304+
Assertions.assertEquals(jwtClaimsSet.getSubject(), consumerDid);
305+
}
306+
307+
@SneakyThrows
308+
@Test
309+
@DisplayName("Test creating STS token without audience claim should fail")
310+
void testCreateStsWithoutAudience() {
311+
String readScope = "read";
312+
String consumerBpn = TestUtils.getRandomBpmNumber();
313+
String providerBpn = TestUtils.getRandomBpmNumber();
314+
String consumerDid = CommonUtils.getDidWeb(walletStubSettings.didHost(), consumerBpn);
315+
String providerDid = CommonUtils.getDidWeb(walletStubSettings.didHost(), providerBpn);
316+
317+
// Create token without audience
318+
JWTClaimsSet tokenWithoutAudience = new JWTClaimsSet.Builder()
319+
.issuer(consumerDid)
320+
.subject(consumerDid)
321+
.issueTime(Date.from(Instant.now()))
322+
.claim(Constants.CREDENTIAL_TYPES, List.of(Constants.MEMBERSHIP_CREDENTIAL))
323+
.claim(Constants.SCOPE, readScope)
324+
.claim(CONSUMER_DID, consumerDid)
325+
.claim(PROVIDER_DID, providerDid)
326+
.claim(Constants.BPN, consumerBpn)
327+
.build();
328+
329+
String tokenWithoutAudienceStr = CommonUtils.signedJWT(tokenWithoutAudience,
330+
keyService.getKeyPair(consumerBpn),
331+
didDocumentService.getOrCreateDidDocument(consumerBpn).getVerificationMethod().getFirst().getId())
332+
.serialize();
333+
334+
// Attempt to create STS without audience should fail
335+
HttpHeaders headers = new HttpHeaders();
336+
headers.add(HttpHeaders.AUTHORIZATION, TestUtils.createAOauthToken(consumerBpn, restTemplate, tokenService, tokenSettings));
337+
338+
CreateCredentialWithoutScopeRequest request = CreateCredentialWithoutScopeRequest.builder()
339+
.signToken(CreateCredentialWithoutScopeRequest.SignToken.builder()
340+
.audience(consumerDid)
341+
.subject(providerDid)
342+
.issuer(providerDid)
343+
.token(tokenWithoutAudienceStr)
344+
.build())
345+
.build();
346+
347+
HttpEntity<CreateCredentialWithoutScopeRequest> entity = new HttpEntity<>(request, headers);
348+
349+
ResponseEntity<StsTokeResponse> response = restTemplate.exchange("/api/sts",
350+
HttpMethod.POST, entity, StsTokeResponse.class);
351+
352+
Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode().value());
353+
}
285354

286355
@SneakyThrows
287356
@Test

0 commit comments

Comments
 (0)