Skip to content

Commit

Permalink
feat: embedded STS (#3529)
Browse files Browse the repository at this point in the history
* feat: implements embedded sts (Secure Token Service)

* pr remarks

* Update spi/common/core-spi/src/main/java/org/eclipse/edc/spi/security/KeyPairFactory.java

Co-authored-by: Jim Marino <jim.marino@gmail.com>

* Update extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java

Co-authored-by: Jim Marino <jim.marino@gmail.com>

---------

Co-authored-by: Jim Marino <jim.marino@gmail.com>
  • Loading branch information
wolf4ood and jimmarino committed Oct 13, 2023
1 parent bd098f5 commit 7876288
Show file tree
Hide file tree
Showing 24 changed files with 730 additions and 108 deletions.
3 changes: 2 additions & 1 deletion core/common/connector-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ dependencies {

implementation(libs.dnsOverHttps)
implementation(libs.bouncyCastle.bcpkixJdk18on)

implementation(libs.nimbus.jwt)

testImplementation(project(":core:common:junit"))
testImplementation(libs.awaitility)
testImplementation(libs.junit.jupiter.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.edc.connector.core.health.HealthCheckServiceConfiguration;
import org.eclipse.edc.connector.core.health.HealthCheckServiceImpl;
import org.eclipse.edc.connector.core.security.DefaultPrivateKeyParseFunction;
import org.eclipse.edc.connector.core.security.KeyPairFactoryImpl;
import org.eclipse.edc.connector.core.validator.JsonObjectValidatorRegistryImpl;
import org.eclipse.edc.core.transform.TypeTransformerRegistryImpl;
import org.eclipse.edc.policy.engine.PolicyEngineImpl;
Expand All @@ -39,7 +40,9 @@
import org.eclipse.edc.spi.command.CommandHandlerRegistry;
import org.eclipse.edc.spi.event.EventRouter;
import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry;
import org.eclipse.edc.spi.security.KeyPairFactory;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.spi.system.ExecutorInstrumentation;
import org.eclipse.edc.spi.system.Hostname;
import org.eclipse.edc.spi.system.ServiceExtension;
Expand Down Expand Up @@ -85,6 +88,9 @@ public class CoreServicesExtension implements ServiceExtension {
@Inject
private PrivateKeyResolver privateKeyResolver;

@Inject
private Vault vault;

@Inject
private EventExecutorServiceContainer eventExecutorServiceContainer;

Expand Down Expand Up @@ -167,6 +173,11 @@ public EventRouter eventRouter(ServiceExtensionContext context) {
return new EventRouterImpl(context.getMonitor(), eventExecutorServiceContainer.getExecutorService());
}

@Provider
public KeyPairFactory keyPairFactory() {
return new KeyPairFactoryImpl(privateKeyResolver, vault);
}

@Provider
public HealthCheckService healthCheckService() {
return healthCheckService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
*/

package org.eclipse.edc.connector.transfer.dataplane.security;
package org.eclipse.edc.connector.core.security;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.Curve;
Expand All @@ -23,6 +23,7 @@
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.security.KeyPairFactory;
import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.spi.security.Vault;
import org.jetbrains.annotations.NotNull;
Expand All @@ -33,16 +34,32 @@
import java.util.Optional;
import java.util.UUID;

public class ConsumerPullKeyPairFactory {
public class KeyPairFactoryImpl implements KeyPairFactory {

private final PrivateKeyResolver privateKeyResolver;
private final Vault vault;

public ConsumerPullKeyPairFactory(PrivateKeyResolver privateKeyResolver, Vault vault) {
public KeyPairFactoryImpl(PrivateKeyResolver privateKeyResolver, Vault vault) {
this.privateKeyResolver = privateKeyResolver;
this.vault = vault;
}

@NotNull
private static Result<PublicKey> convertPemToPublicKey(String pem) {
try {
var jwk = JWK.parseFromPEMEncodedObjects(pem);
if (jwk instanceof RSAKey) {
return Result.success(jwk.toRSAKey().toPublicKey());
} else if (jwk instanceof ECKey) {
return Result.success(jwk.toECKey().toPublicKey());
} else {
return Result.failure(String.format("Public key algorithm %s is not supported", jwk.getAlgorithm().toString()));
}
} catch (JOSEException e) {
return Result.failure("Failed to parse private key: " + e.getMessage());
}
}

public Result<KeyPair> fromConfig(@NotNull String publicKeyAlias, @NotNull String privateKeyAlias) {
return publicKey(publicKeyAlias)
.compose(publicKey -> privateKey(privateKeyAlias)
Expand All @@ -64,7 +81,7 @@ public KeyPair defaultKeyPair() {
@NotNull
private Result<PublicKey> publicKey(String alias) {
return Optional.ofNullable(vault.resolveSecret(alias))
.map(ConsumerPullKeyPairFactory::convertPemToPublicKey)
.map(KeyPairFactoryImpl::convertPemToPublicKey)
.orElse(Result.failure("Failed to resolve public key with alias: " + alias));
}

Expand All @@ -74,20 +91,4 @@ private Result<PrivateKey> privateKey(String alias) {
.map(Result::success)
.orElse(Result.failure("Failed to resolve private key with alias: " + alias));
}

@NotNull
private static Result<PublicKey> convertPemToPublicKey(String pem) {
try {
var jwk = JWK.parseFromPEMEncodedObjects(pem);
if (jwk instanceof RSAKey) {
return Result.success(jwk.toRSAKey().toPublicKey());
} else if (jwk instanceof ECKey) {
return Result.success(jwk.toECKey().toPublicKey());
} else {
return Result.failure(String.format("Public key algorithm %s is not supported", jwk.getAlgorithm().toString()));
}
} catch (JOSEException e) {
return Result.failure("Failed to parse private key: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
*/

package org.eclipse.edc.connector.transfer.dataplane.security;
package org.eclipse.edc.connector.core.security;

import org.eclipse.edc.spi.security.PrivateKeyResolver;
import org.eclipse.edc.spi.security.Vault;
Expand All @@ -29,12 +29,17 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class ConsumerPullKeyPairFactoryTest {
class KeyPairFactoryImplTest {

private final PrivateKeyResolver privateKeyResolver = mock(PrivateKeyResolver.class);
private final Vault vault = mock(Vault.class);

private final ConsumerPullKeyPairFactory factory = new ConsumerPullKeyPairFactory(privateKeyResolver, vault);
private final KeyPairFactoryImpl factory = new KeyPairFactoryImpl(privateKeyResolver, vault);

private static String loadPemFile(String file) throws IOException {
return new String(Objects.requireNonNull(KeyPairFactoryImpl.class.getClassLoader().getResourceAsStream(file))
.readAllBytes());
}

@ParameterizedTest(name = "{index} {1}")
@CsvSource({ "rsa-pubkey.pem, RSA", "ec-pubkey.pem, EC" })
Expand Down Expand Up @@ -80,9 +85,4 @@ void fromConfig_failedToRetrievePublicKey() {

assertThat(result.failed()).isTrue();
}

private static String loadPemFile(String file) throws IOException {
return new String(Objects.requireNonNull(ConsumerPullKeyPairFactoryTest.class.getClassLoader().getResourceAsStream(file))
.readAllBytes());
}
}
9 changes: 9 additions & 0 deletions core/common/connector-core/src/test/resources/rsa-pubkey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi4zQvCGMlQ0N7wsKIU8N
5aExdhxPPiFqUvV27+WtbPdREEVAmOESUeW00+JO2EBSjuIf4ny4yCRhykr8CjqQ
HFN+ehXaTjw8HyOK7izbExdMe0Bb+SoNcduYL6KRLqUp4QF5fym0vTulRPQ/lT3n
IVUfh4BoEasiWc+cP/7y0qDtsjmiDlPUTRi6UJJHDOokS1P800weSRbDMQmX3zFO
+fztK6zklnbBhuZHjmnuIvqKncFvAgs2ZQkuYEhz/dWAqs5Jepyy4S7SZ4stvHzJ
zpwimRHWJEm0XEK56wrGt7V5j63fXvl72KpncWyNHm+2Obru1OrPBGaHm0kNZnCi
jwIDAQAB
-----END PUBLIC KEY-----
1 change: 1 addition & 0 deletions core/common/jwt-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
api(project(":spi:common:jwt-spi"))

implementation(libs.nimbus.jwt)
api(libs.bouncyCastle.bcpkixJdk18on)
}


Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ dependencies {
api(project(":spi:common:identity-trust-spi"))
implementation(project(":spi:common:http-spi"))
implementation(project(":core:common:util"))
implementation(project(":core:common:jwt-core"))
implementation(project(":extensions:common:crypto:jws2020"))
implementation(project(":extensions:common:iam:identity-trust:identity-trust-service"))
implementation(project(":extensions:common:iam:identity-trust:identity-trust-sts-embedded"))
implementation(libs.nimbus.jwt)

testImplementation(testFixtures(project(":spi:common:identity-trust-spi")))
testImplementation(project(":core:common:junit"))
testImplementation(libs.nimbus.jwt)
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,68 @@

package org.eclipse.edc.iam.identitytrust.core;

import org.eclipse.edc.iam.identitytrust.core.service.EmbeddedSecureTokenService;
import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.jwt.TokenGenerationServiceImpl;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.security.KeyPairFactory;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;

import java.security.KeyPair;
import java.time.Clock;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Extension("Identity And Trust Extension to register default services")
public class IatpDefaultServicesExtension implements ServiceExtension {

@Setting(value = "Alias of private key used for signing tokens, retrieved from private key resolver", defaultValue = "A random EC private key")
public static final String STS_PRIVATE_KEY_ALIAS = "edc.iam.sts.privatekey.alias";
@Setting(value = "Alias of public key used for verifying the tokens, retrieved from the vault", defaultValue = "A random EC public key")
public static final String STS_PUBLIC_KEY_ALIAS = "edc.iam.sts.publickey.alias";
// not a setting, it's defined in Oauth2ServiceExtension
private static final String OAUTH_TOKENURL_PROPERTY = "edc.oauth.token.url";
@Setting(value = "Self-issued ID Token expiration in minutes. By default is 5 minutes", defaultValue = "" + IatpDefaultServicesExtension.DEFAULT_STS_TOKEN_EXPIRATION_MIN)
private static final String STS_TOKEN_EXPIRATION = "edc.iam.sts.token.expiration"; // in minutes

private static final int DEFAULT_STS_TOKEN_EXPIRATION_MIN = 5;

@Inject
private KeyPairFactory keyPairFactory;

@Inject
private Clock clock;

@Provider(isDefault = true)
public SecureTokenService createDefaultTokenService(ServiceExtensionContext context) {
context.getMonitor().info("Using the Embedded STS client, as no other implementation was provided.");
var keyPair = keyPairFromConfig(context);
var tokenExpiration = context.getSetting(STS_TOKEN_EXPIRATION, DEFAULT_STS_TOKEN_EXPIRATION_MIN);


if (context.getSetting(OAUTH_TOKENURL_PROPERTY, null) != null) {
context.getMonitor().warning("The property '%s' was configured, but no remote SecureTokenService was found on the classpath. ".formatted(OAUTH_TOKENURL_PROPERTY) +
"This could be an indicator of a configuration problem.");
}

return new EmbeddedSecureTokenService();
return new EmbeddedSecureTokenService(new TokenGenerationServiceImpl(keyPair.getPrivate()), clock, TimeUnit.MINUTES.toSeconds(tokenExpiration));
}

private KeyPair keyPairFromConfig(ServiceExtensionContext context) {
var pubKeyAlias = context.getSetting(STS_PUBLIC_KEY_ALIAS, null);
var privKeyAlias = context.getSetting(STS_PRIVATE_KEY_ALIAS, null);
if (pubKeyAlias == null && privKeyAlias == null) {
context.getMonitor().info(() -> "No public or private key provided for 'STS.' A key pair will be generated (DO NOT USE IN PRODUCTION)");
return keyPairFactory.defaultKeyPair();
}
Objects.requireNonNull(pubKeyAlias, "public key alias");
Objects.requireNonNull(privKeyAlias, "private key alias");
return keyPairFactory.fromConfig(pubKeyAlias, privKeyAlias)
.orElseThrow(failure -> new EdcException(failure.getFailureDetail()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class IdentityAndTrustExtension implements ServiceExtension {

@Provider
public IdentityService createIdentityService(ServiceExtensionContext context) {
return new IdentityAndTrustService(secureTokenService, getIssuerDid(context), presentationVerifier,
return new IdentityAndTrustService(secureTokenService, getIssuerDid(context), context.getParticipantId(), presentationVerifier,
credentialServiceClient, getJwtValidator(), getJwtVerifier());
}

Expand Down

This file was deleted.

0 comments on commit 7876288

Please sign in to comment.