diff --git a/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java b/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java new file mode 100644 index 000000000..a16e7cf2e --- /dev/null +++ b/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java @@ -0,0 +1,211 @@ +/******************************************************************************** + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.tests.transfer; + +import jakarta.json.JsonObject; +import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.iam.identitytrust.spi.model.PresentationResponseMessage; +import org.eclipse.edc.identityhub.spi.store.CredentialStore; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.generator.VerifiablePresentationService; +import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpParticipant; +import org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.IatpParticipantRuntime; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockserver.integration.ClientAndServer; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static org.eclipse.edc.util.io.Ports.getFreePort; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.BPN_SUFFIX; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bnpPolicy; +import static org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpHelperFunctions.configureParticipant; +import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.iatpRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.stsRuntime; +import static org.hamcrest.Matchers.not; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +@EndToEndTest +public class CredentialSpoofTest implements IatpParticipants { + + public static final String MALICIOUS_ACTOR_NAME = "MALICIOUS"; + public static final String MALICIOUS_ACTOR_BPN = MALICIOUS_ACTOR_NAME + BPN_SUFFIX; + protected static final IatpParticipant MALICIOUS_ACTOR = IatpParticipant.Builder.newInstance() + .name(MALICIOUS_ACTOR_NAME) + .id(MALICIOUS_ACTOR_BPN) + .stsUri(STS.stsUri()) + .stsClientId(MALICIOUS_ACTOR_BPN) + .stsClientSecret("client_secret") + .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) + .dimUri(DIM_URI) + .did(IatpParticipants.did(MALICIOUS_ACTOR_NAME)) + .build(); + + @RegisterExtension + protected static final IatpParticipantRuntime MALICIOUS_ACTOR_RUNTIME = iatpRuntime(MALICIOUS_ACTOR.getName(), MALICIOUS_ACTOR.iatpConfiguration(PROVIDER, CONSUMER), MALICIOUS_ACTOR.getKeyPair()); + @RegisterExtension + protected static final IatpParticipantRuntime CONSUMER_RUNTIME = iatpRuntime(CONSUMER.getName(), CONSUMER.iatpConfiguration(PROVIDER, MALICIOUS_ACTOR), CONSUMER.getKeyPair()); + @RegisterExtension + protected static final IatpParticipantRuntime PROVIDER_RUNTIME = iatpRuntime(PROVIDER.getName(), PROVIDER.iatpConfiguration(CONSUMER, MALICIOUS_ACTOR), PROVIDER.getKeyPair()); + @RegisterExtension + protected static final IatpParticipantRuntime STS_RUNTIME = stsRuntime(STS.getName(), STS.stsConfiguration(CONSUMER, PROVIDER, MALICIOUS_ACTOR), STS.getKeyPair()); + private static final Integer MOCKED_CS_SERVICE_PORT = getFreePort(); + protected ClientAndServer server; + + + @BeforeAll + static void prepare() { + + // create the DIDs cache + var dids = new HashMap(); + dids.put(DATASPACE_ISSUER_PARTICIPANT.didUrl(), DATASPACE_ISSUER_PARTICIPANT.didDocument()); + dids.put(CONSUMER.getDid(), CONSUMER.getDidDocument()); + dids.put(PROVIDER.getDid(), PROVIDER.getDidDocument()); + dids.put(MALICIOUS_ACTOR.getDid(), maliciousActorDidDocument(MALICIOUS_ACTOR.getDidDocument())); + + configureParticipant(DATASPACE_ISSUER_PARTICIPANT, CONSUMER, CONSUMER_RUNTIME, dids, STS_RUNTIME); + configureParticipant(DATASPACE_ISSUER_PARTICIPANT, PROVIDER, PROVIDER_RUNTIME, dids, STS_RUNTIME); + configureParticipant(DATASPACE_ISSUER_PARTICIPANT, MALICIOUS_ACTOR, MALICIOUS_ACTOR_RUNTIME, dids, STS_RUNTIME); + + } + + private static DidDocument maliciousActorDidDocument(DidDocument didDocument) { + var service = new Service(); + service.setId("#credential-service"); + service.setType("CredentialService"); + service.setServiceEndpoint("http://%s:%d".formatted("localhost", MOCKED_CS_SERVICE_PORT)); + return DidDocument.Builder.newInstance() + .id(didDocument.getId()) + .verificationMethod(didDocument.getVerificationMethod()) + .service(List.of(service)) + .build(); + } + + @BeforeEach + void setup() { + server = ClientAndServer.startClientAndServer("localhost", getFreePort(), MOCKED_CS_SERVICE_PORT); + } + + @AfterEach + void shutdown() { + server.stop(); + } + + @Test + @DisplayName("Malicious actor should not impersonate a consumer by creating a VP with the consumer membership credential") + void shouldNotImpersonateConsumer_withWrappedConsumerCredential() { + var assetId = "api-asset-1"; + + + Map dataAddress = Map.of( + "baseUrl", "http://mock", + "type", "HttpData", + "contentType", "application/json" + ); + + var presentationService = MALICIOUS_ACTOR_RUNTIME.getService(VerifiablePresentationService.class); + + withMock((membershipCredential) -> presentationService.createPresentation(MALICIOUS_ACTOR.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); + + PROVIDER.createAsset(assetId, Map.of(), dataAddress); + + var policy = createAccessPolicy(CONSUMER.getBpn()); + var accessPolicyId = PROVIDER.createPolicyDefinition(policy); + var contractPolicyId = PROVIDER.createPolicyDefinition(policy); + PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); + + MALICIOUS_ACTOR.getCatalog(PROVIDER) + .log().ifError() + .statusCode(not(200)); + + } + + @Test + @DisplayName("Malicious actor should not impersonate a consumer sending a valid consumer VP") + void shouldNotImpersonateConsumer_withConsumerPresentation() { + var assetId = "api-asset-1"; + + + Map dataAddress = Map.of( + "baseUrl", "http://mock", + "type", "HttpData", + "contentType", "application/json" + ); + + var presentationService = CONSUMER_RUNTIME.getService(VerifiablePresentationService.class); + + withMock((membershipCredential) -> presentationService.createPresentation(CONSUMER.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); + + PROVIDER.createAsset(assetId, Map.of(), dataAddress); + + var policy = createAccessPolicy(CONSUMER.getBpn()); + var accessPolicyId = PROVIDER.createPolicyDefinition(policy); + var contractPolicyId = PROVIDER.createPolicyDefinition(policy); + PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); + + MALICIOUS_ACTOR.getCatalog(PROVIDER) + .log().ifError() + .statusCode(not(200)); + + } + + void withMock(Function> response) { + + var store = CONSUMER_RUNTIME.getService(CredentialStore.class); + + var sokratesMembershipCredential = store.query(QuerySpec.max()).getContent() + .stream().filter(c -> c.getVerifiableCredential().credential().getType().contains("MembershipCredential")) + .findFirst() + .orElseThrow(); + + var transformerRegistry = MALICIOUS_ACTOR_RUNTIME.getService(TypeTransformerRegistry.class); + var jsonLd = MALICIOUS_ACTOR_RUNTIME.getService(JsonLd.class); + + + server.when(request().withMethod("POST").withPath("/presentations/query")).respond((request -> { + var json = response.apply(sokratesMembershipCredential) + .compose(presentation -> transformerRegistry.transform(presentation, JsonObject.class)) + .compose(jsonLd::compact) + .orElseThrow(failure -> new EdcException(failure.getFailureDetail())); + + return response().withStatusCode(200).withBody(json.toString()); + })); + } + + protected JsonObject createAccessPolicy(String bpn) { + return bnpPolicy(bpn); + } + +} diff --git a/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IatpParticipants.java b/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IatpParticipants.java new file mode 100644 index 000000000..f096ffc17 --- /dev/null +++ b/edc-tests/edc-controlplane/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IatpParticipants.java @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.tests.transfer; + +import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.DataspaceIssuer; +import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpParticipant; +import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StsParticipant; + +import java.net.URI; + +import static org.eclipse.edc.util.io.Ports.getFreePort; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_BPN; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; + +public interface IatpParticipants { + + URI DIM_URI = URI.create("http://localhost:" + getFreePort()); + DataspaceIssuer DATASPACE_ISSUER_PARTICIPANT = new DataspaceIssuer(); + StsParticipant STS = StsParticipant.Builder.newInstance() + .id("STS") + .name("STS") + .build(); + IatpParticipant CONSUMER = IatpParticipant.Builder.newInstance() + .name(CONSUMER_NAME) + .id(CONSUMER_BPN) + .stsUri(STS.stsUri()) + .stsClientId(CONSUMER_BPN) + .stsClientSecret("client_secret") + .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) + .dimUri(DIM_URI) + .did(did(CONSUMER_NAME)) + .build(); + + IatpParticipant PROVIDER = IatpParticipant.Builder.newInstance() + .name(PROVIDER_NAME) + .id(PROVIDER_BPN) + .stsUri(STS.stsUri()) + .stsClientId(PROVIDER_BPN) + .stsClientSecret("client_secret") + .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) + .dimUri(DIM_URI) + .did(did(PROVIDER_NAME)) + .build(); + + static String did(String name) { + return "did:example:" + name.toLowerCase(); + } + +}