Skip to content

Commit

Permalink
STS uses JTI Validation Service, records JTI
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Oct 21, 2024
1 parent b2f1389 commit 3845b4a
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
*/

package org.eclipse.edc.verifiablecredentials.jwt;
package org.eclipse.edc.token;

import org.eclipse.edc.jwt.validation.jti.JtiValidationEntry;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.edc.token;

import org.eclipse.edc.jwt.signer.spi.JwsSignerProvider;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.keys.spi.PrivateKeyResolver;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
Expand Down Expand Up @@ -57,4 +58,9 @@ public TokenDecoratorRegistry tokenDecoratorRegistry() {
public JwsSignerProvider defaultSignerProvider() {
return new DefaultJwsSignerProvider(privateKeyResolver);
}

@Provider(isDefault = true)
public JtiValidationStore inMemoryJtiValidationStore() {
return new InMemoryJtiValidationStore();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStoreTestBase;
import org.eclipse.edc.token.InMemoryJtiValidationStore;

class InMemoryJtiValidationStoreTest extends JtiValidationStoreTestBase {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.token.JwtGenerationService;
import org.eclipse.edc.verifiablecredentials.jwt.InMemoryJtiValidationStore;

import java.time.Clock;
import java.util.Map;
Expand All @@ -63,6 +62,8 @@ public class DcpDefaultServicesExtension implements ServiceExtension {
private Clock clock;
@Inject
private JwsSignerProvider externalSigner;
@Inject
private JtiValidationStore jtiValidationStore;

@Provider(isDefault = true)
public SecureTokenService createDefaultTokenService(ServiceExtensionContext context) {
Expand All @@ -79,7 +80,7 @@ public SecureTokenService createDefaultTokenService(ServiceExtensionContext cont
var publicKeyId = context.getSetting(STS_PUBLIC_KEY_ID, null);
var privateKeyAlias = context.getSetting(STS_PRIVATE_KEY_ALIAS, null);

return new EmbeddedSecureTokenService(new JwtGenerationService(externalSigner), () -> privateKeyAlias, () -> publicKeyId, clock, TimeUnit.MINUTES.toSeconds(tokenExpiration));
return new EmbeddedSecureTokenService(new JwtGenerationService(externalSigner), () -> privateKeyAlias, () -> publicKeyId, clock, TimeUnit.MINUTES.toSeconds(tokenExpiration), jtiValidationStore);
}

@Provider(isDefault = true)
Expand Down Expand Up @@ -121,8 +122,4 @@ public ClaimTokenCreatorFunction defaultClaimTokenFunction() {
};
}

@Provider(isDefault = true)
public JtiValidationStore inMemoryJtiValidationStore() {
return new InMemoryJtiValidationStore();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.edc.iam.identitytrust.sts.spi.service.StsClientTokenGeneratorService;
import org.eclipse.edc.iam.identitytrust.sts.spi.store.StsAccountStore;
import org.eclipse.edc.jwt.signer.spi.JwsSignerProvider;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
Expand Down Expand Up @@ -64,6 +65,9 @@ public class StsDefaultServicesExtension implements ServiceExtension {
@Inject(required = false)
private StsClientSecretGenerator stsClientSecretGenerator;

@Inject
private JtiValidationStore jtiValidationStore;

@Override
public String name() {
return NAME;
Expand All @@ -76,14 +80,16 @@ public StsClientTokenGeneratorService clientTokenService(ServiceExtensionContext
(client) -> new JwtGenerationService(jwsSignerProvider),
StsAccount::getPrivateKeyAlias,
clock,
TimeUnit.MINUTES.toSeconds(tokenExpiration));
TimeUnit.MINUTES.toSeconds(tokenExpiration),
jtiValidationStore);
}

@Provider
public StsAccountService clientService() {
return new StsAccountServiceImpl(clientStore, vault, transactionContext, stsClientSecretGenerator());
}


private StsClientSecretGenerator stsClientSecretGenerator() {
return ofNullable(stsClientSecretGenerator)
.orElseGet(RandomStringGenerator::new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccountTokenAdditionalParams;
import org.eclipse.edc.iam.identitytrust.sts.spi.service.StsClientTokenGeneratorService;
import org.eclipse.edc.iam.identitytrust.sts.spi.service.StsTokenGenerationProvider;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.ServiceResult;

Expand All @@ -43,19 +44,21 @@ public class StsClientTokenGeneratorServiceImpl implements StsClientTokenGenerat
private final StsTokenGenerationProvider tokenGenerationProvider;
private final Function<StsAccount, String> keyFunction;
private final Clock clock;
private final JtiValidationStore jtiValidationStore;

public StsClientTokenGeneratorServiceImpl(StsTokenGenerationProvider tokenGenerationProvider, Function<StsAccount, String> keyFunction, Clock clock, long tokenExpiration) {
public StsClientTokenGeneratorServiceImpl(StsTokenGenerationProvider tokenGenerationProvider, Function<StsAccount, String> keyFunction, Clock clock, long tokenExpiration, JtiValidationStore jtiValidationStore) {
this.tokenGenerationProvider = tokenGenerationProvider;
this.keyFunction = keyFunction;
this.clock = clock;
this.tokenExpiration = tokenExpiration;
this.jtiValidationStore = jtiValidationStore;
}

@Override
public ServiceResult<TokenRepresentation> tokenFor(StsAccount client, StsAccountTokenAdditionalParams additionalParams) {

var embeddedTokenGenerator = new EmbeddedSecureTokenService(tokenGenerationProvider.tokenGeneratorFor(client), () -> keyFunction.apply(client), client::getPublicKeyReference,
clock, tokenExpiration);
clock, tokenExpiration, jtiValidationStore);

var initialClaims = Map.of(
ISSUER, client.getDid(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccount;
import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccountTokenAdditionalParams;
import org.eclipse.edc.junit.annotations.ComponentTest;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.keys.KeyParserRegistryImpl;
import org.eclipse.edc.keys.VaultPrivateKeyResolver;
import org.eclipse.edc.keys.keyparsers.JwkParser;
Expand All @@ -31,6 +32,7 @@
import org.eclipse.edc.keys.spi.PrivateKeyResolver;
import org.eclipse.edc.query.CriterionOperatorRegistryImpl;
import org.eclipse.edc.security.token.jwt.DefaultJwsSignerProvider;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.token.JwtGenerationService;
import org.eclipse.edc.transaction.spi.NoopTransactionContext;
Expand All @@ -52,14 +54,17 @@
import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM;
import static org.eclipse.edc.iam.identitytrust.sts.spi.store.fixtures.TestFunctions.createClientBuilder;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.CLIENT_ID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@ComponentTest
public class StsAccountTokenIssuanceIntegrationTest {

private final InMemoryStsAccountStore clientStore = new InMemoryStsAccountStore(CriterionOperatorRegistryImpl.ofDefaults());
private final Vault vault = new InMemoryVault(mock());
private final KeyParserRegistry keyParserRegistry = new KeyParserRegistryImpl();
private final JtiValidationStore jtiValidationStore = mock();
private StsAccountServiceImpl clientService;
private StsClientTokenGeneratorServiceImpl tokenGeneratorService;
private PrivateKeyResolver privateKeyResolver;
Expand All @@ -72,10 +77,12 @@ void setup() {
keyParserRegistry.register(new JwkParser(new ObjectMapper(), mock()));
privateKeyResolver = new VaultPrivateKeyResolver(keyParserRegistry, vault, mock(), mock());

when(jtiValidationStore.storeEntry(any())).thenReturn(StoreResult.success());
tokenGeneratorService = new StsClientTokenGeneratorServiceImpl(
client -> new JwtGenerationService(new DefaultJwsSignerProvider(privateKeyResolver)),
StsAccount::getPrivateKeyAlias,
Clock.systemUTC(), 60 * 5);
Clock.systemUTC(), 60 * 5,
jtiValidationStore);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccountTokenAdditionalParams;
import org.eclipse.edc.iam.identitytrust.sts.spi.service.StsTokenGenerationProvider;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.ServiceFailure;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.token.spi.TokenDecorator;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -39,11 +41,13 @@ public class StsAccountTokenGeneratorServiceImplTest {
public static final long TOKEN_EXPIRATION = 60 * 5;
private final StsTokenGenerationProvider tokenGenerationProvider = mock();
private final TokenGenerationService tokenGenerator = mock();
private final JtiValidationStore jtiValidationStore = mock();
private StsClientTokenGeneratorServiceImpl clientTokenService;

@BeforeEach
void setup() {
clientTokenService = new StsClientTokenGeneratorServiceImpl(tokenGenerationProvider, (client) -> "test-key-id", Clock.systemUTC(), TOKEN_EXPIRATION);
when(jtiValidationStore.storeEntry(any())).thenReturn(StoreResult.success());
clientTokenService = new StsClientTokenGeneratorServiceImpl(tokenGenerationProvider, (client) -> "test-key-id", Clock.systemUTC(), TOKEN_EXPIRATION, jtiValidationStore);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package org.eclipse.edc.iam.identitytrust.sts.embedded;

import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService;
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
import org.eclipse.edc.jwt.validation.jti.JtiValidationEntry;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.token.spi.KeyIdDecorator;
Expand Down Expand Up @@ -53,13 +56,15 @@ public class EmbeddedSecureTokenService implements SecureTokenService {
private final Supplier<String> publicKeyIdSupplier;
private final Clock clock;
private final long validity;
private final JtiValidationStore jtiValidationStore;

public EmbeddedSecureTokenService(TokenGenerationService tokenGenerationService, Supplier<String> privateKeyIdSupplier, Supplier<String> publicKeyIdSupplier, Clock clock, long validity) {
public EmbeddedSecureTokenService(TokenGenerationService tokenGenerationService, Supplier<String> privateKeyIdSupplier, Supplier<String> publicKeyIdSupplier, Clock clock, long validity, JtiValidationStore jtiValidationStore) {
this.tokenGenerationService = tokenGenerationService;
this.privateKeyIdSupplier = privateKeyIdSupplier;
this.publicKeyIdSupplier = publicKeyIdSupplier;
this.clock = clock;
this.validity = validity;
this.jtiValidationStore = jtiValidationStore;
}

@Override
Expand All @@ -68,12 +73,26 @@ public Result<TokenRepresentation> createToken(Map<String, String> claims, @Null
return ofNullable(bearerAccessScope)
.map(scope -> createAndAcceptAccessToken(claims, scope, selfIssuedClaims::put))
.orElse(success())
.compose(v -> recordToken(claims))
.compose(v -> {
var keyIdDecorator = new KeyIdDecorator(publicKeyIdSupplier.get());
return tokenGenerationService.generate(privateKeyIdSupplier.get(), keyIdDecorator, new SelfIssuedTokenDecorator(selfIssuedClaims, clock, validity));
});
}

private Result<Void> recordToken(Map<String, String> claims) {
var jti = claims.get(JwtRegisteredClaimNames.JWT_ID);
if (jti != null) {
var exp = claims.get(JwtRegisteredClaimNames.EXPIRATION_TIME);
var expTime = ofNullable(exp).map(Long::parseLong).orElse(null);
var storeResult = jtiValidationStore.storeEntry(new JtiValidationEntry(jti, expTime));
return storeResult.succeeded()
? Result.success()
: failure("error storing JTI for later validation: %s".formatted(storeResult.getFailureDetail()));
}
return Result.success();
}

private Result<Void> createAndAcceptAccessToken(Map<String, String> claims, String scope, BiConsumer<String, String> consumer) {
return createAccessToken(claims, scope)
.compose(tokenRepresentation -> success(tokenRepresentation.getToken()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.token.JwtGenerationService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -51,9 +53,13 @@
import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SCOPE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class EmbeddedSecureTokenServiceIntegrationTest {

private final JtiValidationStore jtiValidationStore = mock();
private KeyPair keyPair;
private EmbeddedSecureTokenService secureTokenService;

Expand All @@ -66,7 +72,8 @@ private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
void setup() throws NoSuchAlgorithmException {
keyPair = generateKeyPair();
var tokenGenerationService = new JwtGenerationService(s -> Result.success(new RSASSASigner(keyPair.getPrivate())));
secureTokenService = new EmbeddedSecureTokenService(tokenGenerationService, () -> "test-private-keyid", () -> "test-keyid", Clock.systemUTC(), 10 * 60);
when(jtiValidationStore.storeEntry(any())).thenReturn(StoreResult.success());
secureTokenService = new EmbeddedSecureTokenService(tokenGenerationService, () -> "test-private-keyid", () -> "test-keyid", Clock.systemUTC(), 10 * 60, jtiValidationStore);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

package org.eclipse.edc.iam.identitytrust.sts.embedded;

import org.eclipse.edc.jwt.validation.jti.JtiValidationStore;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.token.spi.KeyIdDecorator;
import org.eclipse.edc.token.spi.TokenDecorator;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

Expand All @@ -41,10 +44,16 @@ public class EmbeddedSecureTokenServiceTest {
public static final String TEST_PRIVATEKEY_ID = "test-privatekey-id";
private final TokenGenerationService tokenGenerationService = mock();
private final Supplier<String> keySupplier = () -> TEST_PRIVATEKEY_ID;
private final JtiValidationStore jtiValidationStore = mock();

@BeforeEach
void setup() {
when(jtiValidationStore.storeEntry(any())).thenReturn(StoreResult.success());
}

@Test
void createToken_withoutBearerAccessScope() {
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60);
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60, jtiValidationStore);
var token = TokenRepresentation.Builder.newInstance().token("test").build();

when(tokenGenerationService.generate(eq(TEST_PRIVATEKEY_ID), any(TokenDecorator[].class))).thenReturn(Result.success(token));
Expand All @@ -67,7 +76,7 @@ void createToken_withoutBearerAccessScope() {
void createToken_withBearerAccessScope() {

var claims = Map.of(ISSUER, "testIssuer", AUDIENCE, "aud");
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60);
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60, jtiValidationStore);
var token = TokenRepresentation.Builder.newInstance().token("test").build();

when(tokenGenerationService.generate(eq(TEST_PRIVATEKEY_ID), any(TokenDecorator[].class)))
Expand Down Expand Up @@ -100,7 +109,7 @@ void createToken_error_whenAccessTokenFails() {

var claims = Map.of(ISSUER, "testIssuer", AUDIENCE, "aud");

var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60);
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60, jtiValidationStore);
var token = TokenRepresentation.Builder.newInstance().token("test").build();

when(tokenGenerationService.generate(eq(TEST_PRIVATEKEY_ID), any(TokenDecorator[].class)))
Expand All @@ -124,7 +133,7 @@ void createToken_error_whenAccessTokenFails() {
void createToken_error_whenSelfTokenFails() {
var claims = Map.of(ISSUER, "testIssuer", AUDIENCE, "aud");

var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60);
var sts = new EmbeddedSecureTokenService(tokenGenerationService, keySupplier, () -> "test-key", Clock.systemUTC(), 10 * 60, jtiValidationStore);
var token = TokenRepresentation.Builder.newInstance().token("test").build();

when(tokenGenerationService.generate(eq(TEST_PRIVATEKEY_ID), any(TokenDecorator[].class)))
Expand Down

0 comments on commit 3845b4a

Please sign in to comment.