diff --git a/build.gradle.kts b/build.gradle.kts index 50213a16b..d7286a4ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -113,7 +113,7 @@ allprojects { configDirectory.set(rootProject.file("resources")) //checkstyle violations are reported at the WARN level - this.isShowViolations = System.getProperty("checkstyle.verbose", "false").toBoolean() + this.isShowViolations = System.getProperty("checkstyle.verbose", "true").toBoolean() } diff --git a/edc-extensions/ssi/jws2020-crypto-suite/README.md b/edc-extensions/ssi/jws2020-crypto-suite/README.md new file mode 100644 index 000000000..4a11e93dd --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/README.md @@ -0,0 +1,70 @@ +# JsonWebSignature2020 + +This module extends the [iron-verifiable-credentials library](https://github.com/filip26/iron-verifiable-credentials), +which we use in conjunction with [titanium-ld](https://github.com/filip26/titanium-json-ld/) with an implementation for +the [JsonWebSignature2020](https://www.w3.org/community/reports/credentials/CG-FINAL-lds-jws2020-20220721) crypto suite. + +## Technical aspects + +This implementation is actually mostly glue code between the `iron-verifiable-credentials` lib and the +well-known [Nimbus JOSE lib](https://connect2id.com/products/nimbus-jose-jwt), as all cryptographic primitives are taken +from Nimbus. + +VerifiableCredentials and VerifiablePresentations are processed as JSON(-LD) objects, so some familiarity with JSON-LD +is required. +The entrypoint into the cryptographic suite is the `Vc` class, which allows signing/issuing and verifying JSON-LD +structures. The following samples use explicit types for clarity. These are just some illustrative examples, please +check the `IssuerTests` and the `VerifierTests` for more comprehensive explanations. + +### Sign a VC + +```java +JwsSignature2020Suite suite = new JwsSignature2020Suite(JacksonJsonLd.createObjectMapper()); +JsonObject vc = createVcAsJsonLd(); +JWK keyPair = createKeyPairAsJwk(); +JwkMethod signKeys = new JwkMethod(id,type,controller,keyPair); + +var options = suite.createOptions() + .created(Instant.now()) + .verificationMethod(signKeys) // embeds the proof + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + +Issuer signedVc = Vc.sign(vc, signKeys, options); + +JsonObject compacted = IssuerCompat.compact(signedVc); +``` + +### Verify a VC + +```java +JwsSignature2020Suite suite = new JwsSignature2020Suite(JacksonJsonLd.createObjectMapper()); +JsonObject vc = readSignedVc(); +Verifier result = Vc.verify(vc, suite); + +try { + result.isValid(); +} catch(VerificationError error) { + //handle +} +``` + +## Limitations & Known Issues + +Java 17 [dropped support](https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-es256k-signature) for +the `secp256k1` curve. Alternatively, the BouncyCastle JCA provider could be used. +For this implementation, we chose to forego this at the benefit of a smaller library footprint. There is plenty of other +curves to choose from. + +On a similar note, support for Octet Keypairs (`"OKP"`) has not yet been added to the standard Java JCA, thus an +additional dependency `tink` is needed, +check [here](https://connect2id.com/products/nimbus-jose-jwt/examples/jwk-generation#okp) for details. If that is not +acceptable to you, please add a dependency exclusion to your build script. + +`iron-verifiable-credentials` is not 100% agnostic toward its crypto suites, for example there is +a [hard-coded context](https://github.com/filip26/iron-verifiable-credentials/blob/82d13326c5f64a0f38c75d417ffc263febfd970d/src/main/java/com/apicatalog/vc/processor/Issuer.java#L122) +added to the compacted JSON-LD, which is incorrect. It doesn't negatively impact the resulting JSON-LD, other than +possibly affecting processing times, but unfortunately it also makes it impossible to add more contexts, such +as https://w3id.org/security/suites/jws-2020/v1. We mitigated this with +the [`IssuerCompat.java`](./src/main/java/org/eclipse/edc/security/signature/jws2020/IssuerCompat.java), which should be +used +for compaction. diff --git a/edc-extensions/ssi/jws2020-crypto-suite/build.gradle.kts b/edc-extensions/ssi/jws2020-crypto-suite/build.gradle.kts new file mode 100644 index 000000000..9694fcc9f --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.jwt) + implementation(libs.nimbus.jwt) + implementation(libs.edc.spi.jsonld) + implementation(libs.edc.jsonld) + implementation(libs.edc.util) + // used for the Ed25519 Verifier in conjunction with OctetKeyPairs (OKP) + runtimeOnly(libs.tink) + implementation(libs.jakartaJson) + + implementation(libs.apicatalog.iron.vc) { + exclude("com.github.multiformats") + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/ByteArrayAdapter.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/ByteArrayAdapter.java new file mode 100644 index 000000000..02c98fc20 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/ByteArrayAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.jsonld.lang.Keywords; +import com.apicatalog.ld.schema.adapter.LdValueAdapter; +import jakarta.json.Json; +import jakarta.json.JsonValue; +import org.eclipse.edc.jsonld.spi.JsonLdKeywords; + +class ByteArrayAdapter implements LdValueAdapter { + @Override + public byte[] read(JsonValue value) { + if (value.getValueType().equals(JsonValue.ValueType.OBJECT)) { + var obj = value.asJsonObject(); + return obj.getString(JsonLdKeywords.VALUE).getBytes(); + } + return value.toString().getBytes(); + } + + @Override + public JsonValue write(byte[] value) { + return Json.createObjectBuilder() + .add(Keywords.VALUE, new String(value)) + .build(); + } + +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/IssuerCompat.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/IssuerCompat.java new file mode 100644 index 000000000..ab99e8081 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/IssuerCompat.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.jsonld.JsonLd; +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.ld.DocumentError; +import com.apicatalog.ld.signature.SigningError; +import com.apicatalog.vc.processor.Issuer; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.eclipse.edc.util.reflection.ReflectionUtil; + +import java.net.URI; +import java.util.Arrays; + +/** + * The {@link Issuer} adds the context, but currently that adds hard-coded {@code "https://w3id.org/security/suites/ed25519-2020/v1"}. + * For the Jwk2020 suite we need that to be {@code "https://w3id.org/security/suites/jws-2020/v1"}, so as a temporary workaround we do not + * use {@link Issuer#getCompacted()}, but rather use {@link IssuerCompat#compact(Issuer, String...)}. + */ +public class IssuerCompat { + /** + * Compacts the JSON structure represented by the {@link Issuer} by delegating to {@link JsonLd#compact(Document, URI)}. Note that before compacting, the JSON-LD is expanded, signed, all additional contexts are added + * and then compacted. + *

+ * By default, the following contexts are added automatically: + *

+ * + * @param issuer The {@link Issuer} + * @param additionalContexts Any additional context URIs that should be used for compaction. For Jws2020 it is highly likely that + * @return a JSON-LD structure in compacted format that contains the signed content (e.g. a VC). + */ + public static JsonObject compact(Issuer issuer, String... additionalContexts) { + try { + var expanded = issuer.getExpanded(); + var arrayBuilder = Json.createArrayBuilder(); + Arrays.stream(additionalContexts).forEach(arrayBuilder::add); + var context = arrayBuilder + .add("https://www.w3.org/2018/credentials/v1") + .add("https://w3id.org/security/suites/jws-2020/v1") + .add("https://www.w3.org/ns/did/v1") + .build(); + return JsonLd.compact(JsonDocument.of(expanded), JsonDocument.of(context)).loader(getLoader(issuer)) + .get(); + + } catch (JsonLdError | SigningError | DocumentError e) { + throw new RuntimeException(e); + } + } + + /** + * rather crude hack to obtain the {@link Issuer}'s loader. The EDC util we're using here basically fetches the declared field recursively. + * + * @see ReflectionUtil#getFieldValue(String, Object) + */ + private static DocumentLoader getLoader(Issuer issuer) { + return ReflectionUtil.getFieldValue("loader", issuer); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JsonAdapter.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JsonAdapter.java new file mode 100644 index 000000000..364967a4a --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JsonAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.schema.adapter.LdValueAdapter; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.jsonld.spi.JsonLdKeywords; + +import java.util.Map; + +class JsonAdapter implements LdValueAdapter { + private final ObjectMapper mapper; + + JsonAdapter(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public Object read(JsonValue value) { + var input = value; + if (value instanceof JsonObject) { + var jo = value.asJsonObject(); + input = jo.get(JsonLdKeywords.VALUE); + } + return mapper.convertValue(input, Object.class); + } + + @Override + public JsonValue write(Object value) { + if (value instanceof Map) { + var jo = Json.createObjectBuilder(); + jo.add(JsonLdKeywords.VALUE, Json.createObjectBuilder((Map) value)); + jo.add(JsonLdKeywords.TYPE, JsonLdKeywords.JSON); + return mapper.convertValue(jo.build(), JsonValue.class); + } + return mapper.convertValue(value, JsonValue.class); + } + +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkAdapter.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkAdapter.java new file mode 100644 index 000000000..9a86657d6 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkAdapter.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.schema.LdObject; +import com.apicatalog.ld.schema.LdTerm; +import com.apicatalog.ld.schema.adapter.LdValueAdapter; +import com.apicatalog.ld.signature.method.VerificationMethod; +import com.apicatalog.vc.integrity.DataIntegrity; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static org.eclipse.edc.security.signature.jws2020.Jws2020Schema.JWK_PRIVATE_KEY; +import static org.eclipse.edc.security.signature.jws2020.Jws2020Schema.JWK_PUBLIC_KEY; + +/** + * Adapter that converts between {@link LdObject} and {@link VerificationMethod} + */ +class JwkAdapter implements LdValueAdapter { + + @Override + public VerificationMethod read(LdObject value) { + URI id = value.value(LdTerm.ID); + URI type = value.value(LdTerm.TYPE); + URI controller = value.value(DataIntegrity.CONTROLLER); + var keyProperty = getKeyProperty(value); + var jwk = KeyFactory.create(keyProperty); + return new JwkMethod(id, type, controller, jwk); + } + + + @Override + public LdObject write(VerificationMethod method) { + var result = new HashMap(); + Objects.requireNonNull(method, "VerificationMethod cannot be null!"); + + if (method.id() != null) { + result.put(LdTerm.ID.uri(), method.id()); + } + if (method.type() != null) { + result.put(LdTerm.TYPE.uri(), method.type()); + } + if (method.controller() != null) { + result.put(DataIntegrity.CONTROLLER.uri(), method.controller()); + } + + if (method instanceof JwkMethod ecKeyPair) { + if (ecKeyPair.keyPair() != null) { + result.put(JWK_PUBLIC_KEY.uri(), ecKeyPair.keyPair().toPublicJWK().toJSONObject()); + } + } + + return new LdObject(result); + } + + private Map getKeyProperty(LdObject value) { + if (value.contains(JWK_PRIVATE_KEY)) { + return value.value(JWK_PRIVATE_KEY); + } + return value.value(JWK_PUBLIC_KEY); + } + +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkMethod.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkMethod.java new file mode 100644 index 000000000..9d94317eb --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwkMethod.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.signature.key.KeyPair; +import com.nimbusds.jose.jwk.JWK; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.net.URI; + +record JwkMethod(URI id, URI type, URI controller, JWK keyPair) implements KeyPair { + + @Override + public byte[] privateKey() { + return keyPair != null ? serializeKeyPair(keyPair) : null; + } + + + @Override + public byte[] publicKey() { + return keyPair != null ? serializeKeyPair(keyPair.toPublicJWK()) : null; + } + + private byte[] serializeKeyPair(JWK keyPair) { + try { + var bos = new ByteArrayOutputStream(); + var out = new ObjectOutputStream(bos); + out.writeObject(keyPair); + return bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020CryptoSuite.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020CryptoSuite.java new file mode 100644 index 000000000..09de46660 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020CryptoSuite.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.schema.LdTerm; +import com.apicatalog.ld.signature.CryptoSuite; +import com.apicatalog.ld.signature.primitive.MessageDigest; +import com.apicatalog.ld.signature.primitive.Urdna2015; + +class Jws2020CryptoSuite extends CryptoSuite { + Jws2020CryptoSuite(LdTerm id) { + super(id, new Urdna2015(), new MessageDigest("SHA-256"), new Jws2020SignatureProvider()); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java new file mode 100644 index 000000000..83915b078 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.schema.LdProperty; +import com.apicatalog.ld.schema.LdSchema; +import com.apicatalog.ld.schema.LdTerm; +import com.apicatalog.vc.VcTag; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; + +import static com.apicatalog.ld.schema.LdSchema.id; +import static com.apicatalog.ld.schema.LdSchema.link; +import static com.apicatalog.ld.schema.LdSchema.object; +import static com.apicatalog.ld.schema.LdSchema.property; +import static com.apicatalog.ld.schema.LdSchema.string; +import static com.apicatalog.ld.schema.LdSchema.type; +import static com.apicatalog.ld.schema.LdSchema.xsdDateTime; +import static com.apicatalog.vc.VcSchema.proof; +import static com.apicatalog.vc.VcSchema.verificationMethod; +import static com.apicatalog.vc.VcVocab.SECURITY_VOCAB; +import static com.apicatalog.vc.integrity.DataIntegrity.CHALLENGE; +import static com.apicatalog.vc.integrity.DataIntegrity.CREATED; +import static com.apicatalog.vc.integrity.DataIntegrity.DOMAIN; +import static com.apicatalog.vc.integrity.DataIntegrity.PURPOSE; +import static com.apicatalog.vc.integrity.DataIntegrity.VERIFICATION_METHOD; + +/** + * Internal class that encapsulates all JSON schemas that are relevant for JWS2020, such as the structure of the verification method + */ +class Jws2020Schema { + public static final LdTerm JSON_WEB_KEY_TYPE = LdTerm.create("JsonWebKey2020", SECURITY_VOCAB); + public static final LdTerm JSON_WEB_SIGNATURE_TYPE = LdTerm.create("JsonWebSignature2020", SECURITY_VOCAB); + public static final LdTerm JWS = LdTerm.create("jws", SECURITY_VOCAB); + public static final LdTerm CONTROLLER = LdTerm.create("controller", SECURITY_VOCAB); + public static final LdTerm JWK_PRIVATE_KEY = LdTerm.create("privateKeyJwk", SECURITY_VOCAB); + public static final LdTerm JWK_PUBLIC_KEY = LdTerm.create("publicKeyJwk", SECURITY_VOCAB); + + + /** + * Creates an {@link LdSchema} for the verification method object of JWS2020, which looks roughly like this: + *
+     *     "verificationMethod": [
+     *       {
+     *         "id": "#ovsDKYBjFemIy8DVhc-w2LSi8CvXMw2AYDzHj04yxkc",
+     *         "type": "JsonWebKey2020",
+     *         "controller": "https://example.com/issuer/123",
+     *         "publicKeyJwk": {
+     *           "kty": "OKP",
+     *           "crv": "Ed25519",
+     *           "x": "CV-aGlld3nVdgnhoZK0D36Wk-9aIMlZjZOK2XhPMnkQ"
+     *         }
+     *       }
+     *     ],
+     * 
+ * + * @param mapper The object mapper that is used to deserialize the {@code publicKeyJwk} part. + * @return The {@link LdSchema} that represents the above structure. Never null. + */ + public static LdSchema create(ObjectMapper mapper) { + return proof( + type(JSON_WEB_SIGNATURE_TYPE).required(), + property(CREATED, xsdDateTime()) + .test(created -> Instant.now().isAfter(created)) + .required(), + property(CONTROLLER, link()), + property(PURPOSE, link()).required(), + verificationMethod(VERIFICATION_METHOD, getVerificationMethod(mapper).map(new JwkAdapter())).required(), + property(DOMAIN, string()) + .test((domain, params) -> !params.containsKey(DOMAIN.name()) || params.get(DOMAIN.name()).equals(domain)), + property(CHALLENGE, string()), + property(JWS, new ByteArrayAdapter(), VcTag.ProofValue.name()) + ); + } + + private static LdSchema getVerificationMethod(ObjectMapper mapper) { + return jsonWebKeySchema(mapper); + } + + private static LdSchema jsonWebKeySchema(ObjectMapper mapper) { + return new LdSchema( + object( + id().required(), + type(JSON_WEB_KEY_TYPE), + property(CONTROLLER, link()), + jwkPublicKey(mapper) + )); + } + + private static LdProperty jwkPublicKey(ObjectMapper mapper) { + return property(JWK_PUBLIC_KEY, new JsonAdapter(mapper)); + } + + private static LdProperty jwkPrivateKey(ObjectMapper mapper) { + return property(JWK_PRIVATE_KEY, new JsonAdapter(mapper)); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020SignatureProvider.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020SignatureProvider.java new file mode 100644 index 000000000..b1bc11800 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020SignatureProvider.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.signature.KeyGenError; +import com.apicatalog.ld.signature.SigningError; +import com.apicatalog.ld.signature.VerificationError; +import com.apicatalog.ld.signature.algorithm.SignatureAlgorithm; +import com.apicatalog.ld.signature.key.KeyPair; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSObject; +import com.nimbusds.jose.Payload; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jose.jwk.RSAKey; +import org.eclipse.edc.spi.EdcException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.text.ParseException; +import java.util.Collections; + +class Jws2020SignatureProvider implements SignatureAlgorithm { + + @Override + public void verify(byte[] publicKey, byte[] signature, byte[] data) throws VerificationError { + + try { + var jwk = deserialize(publicKey); + if (jwk == null) { + throw new UnsupportedOperationException("Cannot deserialize public key, expected JWK format"); + } + var verifier = KeyFactory.createVerifier(jwk); + + var detachedPayload = new Payload(data); + var jws = new String(signature); + + var parsedJwsObject = JWSObject.parse(jws, detachedPayload); + var isValid = parsedJwsObject.verify(verifier); + + if (!isValid) { + throw new VerificationError(VerificationError.Code.InvalidSignature); + } + + } catch (JOSEException | ParseException e) { + throw new VerificationError(VerificationError.Code.InvalidSignature, e); + } + } + + @Override + public byte[] sign(byte[] privateKey, byte[] data) throws SigningError { + + try { + var keyPair = deserialize(privateKey); + if (keyPair == null) { + throw new UnsupportedOperationException("Cannot deserialize key pair, expected JWK format"); + } + // Create and sign JWS + JWSHeader header = new JWSHeader.Builder(from(keyPair)) + .base64URLEncodePayload(false) + .criticalParams(Collections.singleton("b64")) + .build(); + + var detachedPayload = new Payload(data); + var jwsObject = new JWSObject(header, detachedPayload); + jwsObject.sign(KeyFactory.createSigner(keyPair)); + + boolean isDetached = true; + String jws = jwsObject.serialize(isDetached); + return jws.getBytes(); + + } catch (JOSEException e) { + throw new SigningError(SigningError.Code.UnsupportedCryptoSuite, e); + } + } + + @Override + public KeyPair keygen(int length) throws KeyGenError { + return null; + } + + /** + * Attempt to determine the {@link JWSAlgorithm} from the curve that is being used in the ECKey pair + */ + private JWSAlgorithm from(JWK keyPair) { + if (keyPair instanceof ECKey eckey) { + var jwsAlgorithm = JWSAlgorithm.Family.EC.stream() + .filter(algo -> Curve.forJWSAlgorithm(algo).contains(eckey.getCurve())) + .findFirst(); + return jwsAlgorithm.orElseThrow(() -> new EdcException("Could not determine JWSAlgorithm for Curve " + eckey.getCurve())); + } else if (keyPair instanceof OctetKeyPair okp) { + var jwsAlgorithm = JWSAlgorithm.Family.ED.stream() + .filter(algo -> Curve.forJWSAlgorithm(algo).contains(okp.getCurve())) + .findFirst(); + return jwsAlgorithm.orElseThrow(() -> new EdcException("Could not determine JWSAlgorithm for Curve " + okp.getCurve())); + } else if (keyPair instanceof RSAKey) { + return JWSAlgorithm.RS512; + } + return null; + } + + private JWK deserialize(byte[] privateKey) { + ByteArrayInputStream bis = new ByteArrayInputStream(privateKey); + try { + ObjectInputStream in = new ObjectInputStream(bis); + return (JWK) in.readObject(); + } catch (IOException | ClassNotFoundException e) { + return null; + } + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignature2020Suite.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignature2020Suite.java new file mode 100644 index 000000000..fd515dd3b --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignature2020Suite.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.schema.LdSchema; +import com.apicatalog.ld.schema.LdTerm; +import com.apicatalog.ld.signature.CryptoSuite; +import com.apicatalog.ld.signature.SignatureSuite; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; + +import java.net.URI; + +/** + * {@link SignatureSuite} that provides cryptographic facilities and a key schema for Json Web Signature 2020. + */ +public final class JwsSignature2020Suite implements SignatureSuite { + static final URI CONTEXT = URI.create("https://w3id.org/security/suites/jws-2020/v1"); + private final Jws2020CryptoSuite cryptoSuite; + private final ObjectMapper mapper; + + /** + * Creates a new {@link JwsSignature2020Suite} using an object mapper. That mapper is needed because parts of the schema are plain JSON. + * + * @param mapper a JSON-aware {@link ObjectMapper}, e.g. using {@link JacksonJsonLd#createObjectMapper()} + * @see Jws2020Schema + */ + public JwsSignature2020Suite(ObjectMapper mapper) { + this.mapper = mapper; + cryptoSuite = new Jws2020CryptoSuite(LdTerm.ID); + } + + @Override + public LdTerm getId() { + return Jws2020Schema.JSON_WEB_SIGNATURE_TYPE; + } + + @Override + public URI getContext() { + return CONTEXT; + } + + @Override + public LdSchema getSchema() { + return Jws2020Schema.create(mapper); + } + + @Override + public CryptoSuite getCryptoSuite() { + return cryptoSuite; + } + + @Override + public JwsSignatureProofOptions createOptions() { + return new JwsSignatureProofOptions(this); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignatureProofOptions.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignatureProofOptions.java new file mode 100644 index 000000000..70c006b92 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/JwsSignatureProofOptions.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.vc.integrity.DataIntegrityProofOptions; + +/** + * Proof options for Jws2020 + */ +public class JwsSignatureProofOptions extends DataIntegrityProofOptions { + /** + * Create a new proof options instance + * + * @param suite The {@link JwsSignature2020Suite} for which the options are created. + */ + public JwsSignatureProofOptions(JwsSignature2020Suite suite) { + super(suite); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/KeyFactory.java b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/KeyFactory.java new file mode 100644 index 000000000..f66cfa7f5 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/main/java/org/eclipse/edc/security/signature/jws2020/KeyFactory.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.ECDSAVerifier; +import com.nimbusds.jose.crypto.Ed25519Signer; +import com.nimbusds.jose.crypto.Ed25519Verifier; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jose.jwk.RSAKey; + +import java.text.ParseException; +import java.util.Map; +import java.util.Objects; + +import static java.lang.String.format; + +/** + * Utility class that has two basic duties: + *
    + *
  • Create cryptographic keys represented in JWK format ({@link JWK}) out of JSON or JSON-like objects
  • + *
  • Create {@link JWSSigner}s and {@link JWSVerifier}s for any given key represented in {@link JWK} format.
  • + *
+ *

+ * For this, the well-known Nimbus JOSE+JWT library is used. + */ +public class KeyFactory { + + /** + * Creates a {@link JWK} out of a map that represents a JSON structure. + * + * @param jsonObject The map containing the JSON + * @return the corresponding key. + * @throws RuntimeException if the JSON was malformed, or the JWK type was unknown. Typically, this wraps a {@link ParseException} + */ + public static JWK create(Map jsonObject) { + if (jsonObject == null) return null; + try { + return JWK.parse(jsonObject); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates a {@link JWK} out of a JSON string containing the key properties + * + * @param json The string containing plain JSON + * @return the corresponding key. + * @throws RuntimeException if the JSON was malformed, or the JWK type was unknown. Typically, this wraps a {@link ParseException} + */ + public static JWK create(String json) { + if (json == null) return null; + try { + return JWK.parse(json); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Creates a {@link JWSVerifier} from the base class {@link JWK}. Currently only supports EC, OKP and RSA keys. + * + * @param jwk The {@link JWK} for which the {@link JWSVerifier} is to be created. + * @return the {@link JWSVerifier} + * @throws UnsupportedOperationException if the verifier could not be created, in which case the root cause would be {@link JOSEException} + */ + public static JWSVerifier createVerifier(JWK jwk) { + Objects.requireNonNull(jwk, "jwk cannot be null"); + var value = jwk.getKeyType().getValue(); + try { + return switch (value) { + case "EC" -> new ECDSAVerifier((ECKey) jwk); + case "OKP" -> new Ed25519Verifier((OctetKeyPair) jwk); + case "RSA" -> new RSASSAVerifier((RSAKey) jwk); + default -> + throw new UnsupportedOperationException(format("Cannot create JWSVerifier for JWK-type [%s], currently only supporting EC, OKP and RSA", value)); + }; + } catch (JOSEException ex) { + throw new UnsupportedOperationException(ex); + } + } + + /** + * Creates a {@link JWSSigner} from the base class {@link JWK}. Currently only supports EC, OKP and RSA keys. + * + * @param jwk The {@link JWK} for which the {@link JWSSigner} is to be created. + * @return the {@link JWSSigner} + * @throws UnsupportedOperationException if the signer could not be created, in which case the root cause would be {@link JOSEException} + */ + public static JWSSigner createSigner(JWK jwk) { + var value = jwk.getKeyType().getValue(); + try { + return switch (value) { + case "EC" -> new ECDSASigner((ECKey) jwk); + case "OKP" -> new Ed25519Signer((OctetKeyPair) jwk); + case "RSA" -> new RSASSASigner((RSAKey) jwk); + default -> + throw new UnsupportedOperationException(format("Cannot create JWSVerifier for JWK-type [%s], currently only supporting EC, OKP and RSA", value)); + }; + } catch (JOSEException ex) { + throw new UnsupportedOperationException(ex); + } + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/IssuerTests.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/IssuerTests.java new file mode 100644 index 000000000..2bdc15b8e --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/IssuerTests.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.jsonld.loader.SchemeRouter; +import com.apicatalog.ld.DocumentError; +import com.apicatalog.ld.signature.SigningError; +import com.apicatalog.vc.Vc; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URI; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.time.Instant; +import java.util.Date; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.security.signature.jws2020.TestFunctions.createKeyPair; +import static org.eclipse.edc.security.signature.jws2020.TestFunctions.readResourceAsJson; +import static org.eclipse.edc.security.signature.jws2020.TestFunctions.readResourceAsString; + +class IssuerTests { + + private final JwsSignature2020Suite jws2020suite = new JwsSignature2020Suite(JacksonJsonLd.createObjectMapper()); + //used to load remote data from a local directory + private final TestResourcesLoader loader = new TestResourcesLoader("https://org.eclipse.tractusx/", "jws2020/issuing/", SchemeRouter.defaultInstance()); + + @DisplayName("t0001: a simple credential to sign (EC Key)") + @Test + void signSimpleCredential_ecKey() throws SigningError, DocumentError { + var vc = readResourceAsJson("jws2020/issuing/0001_vc.json"); + var keypair = createKeyPair(KeyFactory.create(readResourceAsString("jws2020/issuing/private-key.json"))); + + var verificationMethodUrl = "https://org.eclipse.tractusx/verification-method"; + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(new JwkMethod(URI.create(verificationMethodUrl), null, null, null)) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vc, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected a String!").isInstanceOf(JsonString.class); + assertThat(((JsonString) verificationMethod).getString()).isEqualTo(verificationMethodUrl); + } + + @DisplayName("t0001: a simple credential to sign (RSA Key)") + @ParameterizedTest(name = "keySize = {0} bits") + @ValueSource(ints = {2048, 3072, 4096}) + void signSimpleCredential_rsaKey(int keysize) throws SigningError, DocumentError, NoSuchAlgorithmException { + var vc = readResourceAsJson("jws2020/issuing/0001_vc.json"); + + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keysize); + var keyPair = gen.generateKeyPair(); + + var jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .privateKey((RSAPrivateKey) keyPair.getPrivate()) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .issueTime(new Date()) + .build(); + var keypair = createKeyPair(jwk); + + var verificationMethodUrl = "https://org.eclipse.tractusx/verification-method"; + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(new JwkMethod(URI.create(verificationMethodUrl), null, null, null)) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vc, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected a String!").isInstanceOf(JsonString.class); + assertThat(((JsonString) verificationMethod).getString()).isEqualTo(verificationMethodUrl); + } + + @DisplayName("t0001: a simple credential to sign (OctetKeyPair)") + @Test + void signSimpleCredential_octetKeyPair() throws SigningError, DocumentError, JOSEException { + var vc = readResourceAsJson("jws2020/issuing/0001_vc.json"); + + var jwk = new OctetKeyPairGenerator(Curve.Ed25519).generate(); + var keypair = createKeyPair(jwk); + + var verificationMethodUrl = "https://org.eclipse.tractusx/verification-method"; + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(new JwkMethod(URI.create(verificationMethodUrl), null, null, null)) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vc, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected a String!").isInstanceOf(JsonString.class); + assertThat(((JsonString) verificationMethod).getString()).isEqualTo(verificationMethodUrl); + } + + @DisplayName("t0002: compacted signed credential") + @Test + void signCompactedCredential() { + // nothing to do here, it's the same as above + } + + @DisplayName("t0003: signed embedded verificationMethod") + @Test + void signEmbeddedVerificationMethod() throws SigningError, DocumentError { + var vc = readResourceAsJson("jws2020/issuing/0001_vc.json"); + var keypair = createKeyPair(KeyFactory.create(readResourceAsString("jws2020/issuing/private-key.json"))); + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(keypair) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vc, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected an Object!").isInstanceOf(JsonObject.class); + assertThat(verificationMethod.asJsonObject().get("publicKeyJwk")) + .isInstanceOf(JsonObject.class) + .satisfies(jv -> { + assertThat(jv.asJsonObject().get("x")).isNotNull(); + assertThat(jv.asJsonObject().get("crv")).isNotNull(); + assertThat(jv.asJsonObject().get("kty")).isNotNull(); + }); + } + + @DisplayName("t0004: a credential with DID key as verification method") + @Test + void signVerificationDidKey() throws SigningError, DocumentError { + var vc = readResourceAsJson("jws2020/issuing/0001_vc.json"); + var eckey = (ECKey) KeyFactory.create(""" + { + "kty": "EC", + "d": "UEUJVbKZC3vR-y65gXx8NZVnE0QD5xe6qOk4eiObj-qVOg5zqt9zc0d6fdu4mUuu", + "use": "sig", + "crv": "P-384", + "x": "l6IS348kIFEANYl3CWueMYVXcZmK0eMI0vejkF1GHbl77dOZuZwi9L2IQmuA27ux", + "y": "m-8s5FM8Tn00OKVFxE-wfCs3J2keE2EBAYYZgAmfI1LCRD9iU2LBced-EBK18Da9", + "alg": "ES384" + } + """); + var keypair = createKeyPair(eckey); + + // check https://w3c-ccg.github.io/did-method-key/#create for details + var didKey = "did:key:zC2zU1wUHhYYX4CDwNwky9f5jtSvp5aQy5aNRQMHEdpK5xkJMy6TcMbWBP3scHbR6hhidR3RRjfAA7cuLxjydXgEiZUzRzguozYFeR3G6SzjAwswJ6hXKBWhFEHm2L6Rd6GRAw8r3kyPovxvcabdMF2gBy5TAioY1mVYFeT6"; + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(new JwkMethod(URI.create(didKey), null, null, null)) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vc, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected a String!").isInstanceOf(JsonString.class); + assertThat(((JsonString) verificationMethod).getString()).isEqualTo(didKey); + + } + + @DisplayName("t0005: compacted signed presentation") + @Test + void signCompactedPresentation() throws SigningError, DocumentError { + var vp = readResourceAsJson("jws2020/issuing/0005_vp_compacted_signed.json"); + + var keypair = createKeyPair(KeyFactory.create(readResourceAsString("jws2020/issuing/private-key.json"))); + + var verificationMethodUrl = "https://org.eclipse.tractusx/verification-method"; + + var proofOptions = jws2020suite.createOptions() + .created(Instant.parse("2022-12-31T23:00:00Z")) + .verificationMethod(new JwkMethod(URI.create(verificationMethodUrl), null, null, null)) + .purpose(URI.create("https://w3id.org/security#assertionMethod")); + + + var issuer = Vc.sign(vp, keypair, proofOptions).loader(loader); + + // would throw an exception + var compacted = IssuerCompat.compact(issuer, "https://www.w3.org/ns/did/v1"); + var verificationMethod = compacted.getJsonObject("sec:proof").get("verificationMethod"); + + assertThat(verificationMethod).describedAs("Expected a String!").isInstanceOf(JsonString.class); + assertThat(((JsonString) verificationMethod).getString()).isEqualTo(verificationMethodUrl); + } + +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/JsonAdapterTest.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/JsonAdapterTest.java new file mode 100644 index 000000000..30ed59304 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/JsonAdapterTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class JsonAdapterTest { + + private final JsonAdapter adapter = new JsonAdapter(JacksonJsonLd.createObjectMapper()); + + @Test + void read() { + var jo = Json.createValue("foobar"); + var result = adapter.read(jo); + assertThat(result).isEqualTo("foobar"); + } + + @Test + void read_jsonObjectWithValue() { + var jo = Json.createObjectBuilder() + .add("@type", "test-type") + .add("@value", "test-value") + .build(); + var result = adapter.read(jo); + assertThat(result).isEqualTo("test-value"); + } + + @Test + void write() { + var obj = "test-string"; + var result = adapter.write(obj); + assertThat(result).isInstanceOf(JsonString.class); + } + + @Test + void write_map() { + var map = Map.of("key1", "value1", "key2", "value2"); + var result = adapter.write(map); + assertThat(result).isInstanceOf(JsonObject.class).extracting(JsonValue::asJsonObject).matches(jo -> jo.size() == 2); + } +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/KeyFactoryTest.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/KeyFactoryTest.java new file mode 100644 index 000000000..2fa2468b0 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/KeyFactoryTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.Curve; +import com.nimbusds.jose.jwk.ECKey; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.security.signature.jws2020.TestFunctions.readResourceAsString; + +class KeyFactoryTest { + + @Test + void create_ecKey() throws JsonProcessingException { + var json = """ + { + "kty": "EC", + "crv": "P-384", + "x": "eQbMauiHc9HuiqXT894gW5XTCrOpeY8cjLXAckfRtdVBLzVHKaiXAAxBFeVrSB75", + "y": "YOjxhMkdH9QnNmGCGuGXJrjAtk8CQ1kTmEEi9cg2R9ge-zh8SFT1Xu6awoUjK5Bv", + "d": "dXghMAzYZmv46SNRuxmfDIuAlv7XIhvlkPzW3vXsopB1ihWp47tx0hqjZmYO6fJa" + } + """; + + assertThat(KeyFactory.create(json)).isInstanceOf(ECKey.class).extracting(JWK::isPrivate).isEqualTo(true); + var map = new ObjectMapper().readValue(json, Map.class); + assertThat(KeyFactory.create(map)).isInstanceOf(ECKey.class).extracting(JWK::isPrivate).isEqualTo(true); + } + + @Test + void create_rsa() throws JsonProcessingException { + // the RSA key would violate the Checkstyle line length constraint + var json = readResourceAsString("rsakey.json"); + + assertThat(KeyFactory.create(json)).isInstanceOf(RSAKey.class).extracting(JWK::isPrivate).isEqualTo(true); + var map = new ObjectMapper().readValue(json, Map.class); + assertThat(KeyFactory.create(map)).isInstanceOf(RSAKey.class).extracting(JWK::isPrivate).isEqualTo(true); + } + + @Test + void create_okp() throws JsonProcessingException { + var json = """ + { + "kty" : "OKP", + "crv" : "Ed25519", + "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", + "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "use" : "sig", + "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" + } + """; + + assertThat(KeyFactory.create(json)).isInstanceOf(OctetKeyPair.class).extracting(JWK::isPrivate).isEqualTo(true); + var map = new ObjectMapper().readValue(json, Map.class); + assertThat(KeyFactory.create(map)).isInstanceOf(OctetKeyPair.class).extracting(JWK::isPrivate).isEqualTo(true); + } + + @Test + void create_invalidJson() throws JsonProcessingException { + // JSON misses the "crv" property + var json = """ + { + "kty": "EC", + "x": "eQbMauiHc9HuiqXT894gW5XTCrOpeY8cjLXAckfRtdVBLzVHKaiXAAxBFeVrSB75", + "y": "YOjxhMkdH9QnNmGCGuGXJrjAtk8CQ1kTmEEi9cg2R9ge-zh8SFT1Xu6awoUjK5Bv" + } + """; + + assertThatThrownBy(() -> assertThat(KeyFactory.create(json))).isInstanceOf(RuntimeException.class).rootCause().isInstanceOf(ParseException.class); + + var map = new ObjectMapper().readValue(json, Map.class); + assertThatThrownBy(() -> assertThat(KeyFactory.create(map))).isInstanceOf(RuntimeException.class).rootCause().isInstanceOf(ParseException.class); + } + + @ParameterizedTest(name = "{1}") + @ArgumentsSource(ValidJwkProvider.class) + void createVerifier(JWK validJwk, String name) { + assertThat(KeyFactory.createVerifier(validJwk.toPublicJWK())).isNotNull(); + } + + @ParameterizedTest(name = "{1}") + @ArgumentsSource(ValidJwkProvider.class) + void createSigner(JWK validJwk, String name) { + assertThat(KeyFactory.createSigner(validJwk)).isNotNull(); + } + + private static class ValidJwkProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of(createEcKey(Curve.P_256), "EC Key with P256 Curve"), + Arguments.of(createEcKey(Curve.P_384), "EC Key with P384 Curve"), + Arguments.of(createEcKey(Curve.P_521), "EC Key with P512 Curve"), + Arguments.of(createOkp(), "Octet Key Pair"), + Arguments.of(createRsaKey(2048), "RSA Key, 2048 bit"), + Arguments.of(createRsaKey(4096), "RSA Key, 4096 bit") + ); + } + + private RSAKey createRsaKey(int keysize) { + try { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(keysize); + KeyPair keyPair = gen.generateKeyPair(); + + return new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) + .privateKey((RSAPrivateKey) keyPair.getPrivate()) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .issueTime(new Date()) + .build(); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private OctetKeyPair createOkp() { + try { + return new OctetKeyPairGenerator(Curve.Ed25519) + .keyUse(KeyUse.SIGNATURE) // indicate the intended use of the key (optional) + .keyID(UUID.randomUUID().toString()) // give the key a unique ID (optional) + .issueTime(new Date()) // issued-at timestamp (optional) + .generate(); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + + } + + private ECKey createEcKey(Curve curve) { + try { + KeyPairGenerator gen = KeyPairGenerator.getInstance("EC"); + gen.initialize(curve.toECParameterSpec()); + KeyPair keyPair = gen.generateKeyPair(); + + return new ECKey.Builder(curve, (ECPublicKey) keyPair.getPublic()) + .privateKey((ECPrivateKey) keyPair.getPrivate()) + .build(); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + } +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestFunctions.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestFunctions.java new file mode 100644 index 000000000..6f403b4b6 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestFunctions.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.ld.signature.key.KeyPair; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.jwk.JWK; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; + +import java.io.IOException; +import java.net.URI; +import java.util.Objects; +import java.util.UUID; + +class TestFunctions { + + private static final ObjectMapper MAPPER = JacksonJsonLd.createObjectMapper(); + + static KeyPair createKeyPair(JWK jwk) { + var id = URI.create("https://org.eclipse.tractusx/keys/" + UUID.randomUUID()); + var type = URI.create("https://w3id.org/security#JsonWebKey2020"); + return new JwkMethod(id, type, null, jwk); + } + + static JsonObject readResourceAsJson(String name) { + try { + return MAPPER.readValue(Thread.currentThread().getContextClassLoader().getResourceAsStream(name), JsonObject.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static String readResourceAsString(String name) { + try (var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name)) { + return new String(Objects.requireNonNull(stream).readAllBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestResourcesLoader.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestResourcesLoader.java new file mode 100644 index 000000000..5f3bfff5c --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/TestResourcesLoader.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; + +import java.io.IOException; +import java.net.URI; + +/** + * JSON-LD document loader that allows to "redirect" the loading of remote documents (contexts,...). + * For example, referencing a remote context, or a remote verificationMethod would fail, if that document doesn't exist, but we need it + * for testing, so we can "redirect" the pointer to the local test resources folder. + */ +class TestResourcesLoader implements DocumentLoader { + private final String base; + private final DocumentLoader baseLoader; + private final String resourcePath; + + TestResourcesLoader(String base, String resourcePath, DocumentLoader baseLoader) { + this.base = base; + this.resourcePath = resourcePath; + this.baseLoader = baseLoader; + } + + @Override + public Document loadDocument(URI uri, DocumentLoaderOptions options) throws JsonLdError { + Document document; + var url = uri.toString(); + if (url.startsWith(base)) { + try (var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(rewrite(uri))) { + document = JsonDocument.of(is); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } else { + document = baseLoader.loadDocument(uri, options); + } + return document; + } + + private String rewrite(URI url) { + var path = resourcePath + url.toString().replace(base, ""); + if (!path.endsWith(".json")) { + path += ".json"; + } + return path; + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/VerifierTests.java b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/VerifierTests.java new file mode 100644 index 000000000..8634342c6 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/java/org/eclipse/edc/security/signature/jws2020/VerifierTests.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.security.signature.jws2020; + +import com.apicatalog.jsonld.loader.SchemeRouter; +import com.apicatalog.ld.DocumentError; +import com.apicatalog.ld.signature.VerificationError; +import com.apicatalog.vc.Vc; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.security.signature.jws2020.TestFunctions.readResourceAsJson; + +class VerifierTests { + + private final JwsSignature2020Suite jws2020suite = new JwsSignature2020Suite(JacksonJsonLd.createObjectMapper()); + //used to load remote data from a local directory + private final TestResourcesLoader loader = new TestResourcesLoader("https://org.eclipse.tractusx/", "jws2020/verifying/", SchemeRouter.defaultInstance()); + + @DisplayName("t0001: valid signed VC") + @Test + void verifyValidVc() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0001_vc.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatNoException().isThrownBy(result::isValid); + } + + @DisplayName("t0002: forged credentials subject") + @Test + void verify_forgedSubject() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0002_vc_forged.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatThrownBy(result::isValid).isInstanceOf(VerificationError.class); + } + + @DisplayName("t0003: valid VC with embedded verification method") + @Test + void verifyVc_withEmbeddedVerificationMethod() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0003_vc_embedded.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatNoException().isThrownBy(result::isValid); + } + + @DisplayName("t0004: proof set of two valid proofs") + @Test + void verify_multipleValidProofs() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0004_vc_two_valid_proofs.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatNoException().isThrownBy(result::isValid); + } + + @DisplayName("t0005: proof set having one forged proof") + @Test + void verify_oneForgedProof() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0005_vc_one_forged_proof.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatThrownBy(result::isValid).isInstanceOf(VerificationError.class); + } + + /** + * The did:key method is not yet supported, since it is only a community draft, and implementability in conjunction with JWS2020 is + * unclear. Furthermore, the "did:key" implementation relies + * on this Multibase library, which is not available from MavenCentral. + *

+ * The biggest challenge with Jws will be to reconstruct the key type/curve from just the public key. + */ + @Disabled("did:key is not supported") + @DisplayName("t0006: DID key as verification method (not yet supported)") + @Test + void verify_didKeyAsVerificationMethod() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0006_vc_did_key.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatThrownBy(result::isValid).isInstanceOf(UnsupportedOperationException.class) + .hasMessage("Cannot deserialize public key, expected JWK format"); + } + + @DisplayName("t0007: valid signed VP") + @Test + void verify_validSignedVp() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0006_vp_compacted.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatNoException().isThrownBy(result::isValid); + } + + @DisplayName("t0008: forged signed VP") + @Test + void verify_forgedSignedVp() throws VerificationError, DocumentError { + var vc = readResourceAsJson("jws2020/verifying/0007_vp_compacted_forged.json"); + var result = Vc.verify(vc, jws2020suite).loader(loader); + assertThatThrownBy(result::isValid).isInstanceOf(VerificationError.class); + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0001_vc.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0001_vc.json new file mode 100644 index 000000000..d46429d2f --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0001_vc.json @@ -0,0 +1,21 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0003_vc_embedded.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0003_vc_embedded.json new file mode 100644 index 000000000..d46429d2f --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0003_vc_embedded.json @@ -0,0 +1,21 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0004_vc_did_key.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0004_vc_did_key.json new file mode 100644 index 000000000..46d55fdd3 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0004_vc_did_key.json @@ -0,0 +1,21 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0005_vp_compacted_signed.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0005_vp_compacted_signed.json new file mode 100644 index 000000000..124db8952 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/0005_vp_compacted_signed.json @@ -0,0 +1,38 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + }, + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] + } + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/businessPartnerData.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/businessPartnerData.json new file mode 100644 index 000000000..b36e7cd46 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/businessPartnerData.json @@ -0,0 +1,177 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "BpnCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "bpn": "http://schema.org/identifier" + } + }, + "MembershipCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#MembershipCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "ex": "https://example.org/examples#", + "startTime": { + "@id": "https://schema.org/startTime", + "@type": "https://schema.org/DateTime" + }, + "memberOf": { + "@id": "https://schema.org/memberOf", + "@type": "https://schema.org/Text" + }, + "status": { + "@id": "ex:status", + "@type": "https://schema.org/Text" + } + } + }, + "NameCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#NameCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "value": { + "@id": "ex:value", + "@type": "https://schema.org/Text" + }, + "name": { + "@id": "ex:name", + "@type": "https://schema.org/Text" + }, + "shortName": { + "@id": "ex:shortName", + "@type": "https://schema.org/Text" + }, + "fipsCode": { + "@id": "ex:fipsCode", + "@type": "https://schema.org/Text" + }, + "number": { + "@id": "ex:number", + "@type": "https://schema.org/Text" + }, + "direction": { + "@id": "ex:direction", + "@type": "https://schema.org/Text" + }, + "nameType": { + "@id": "ex:nameType", + "@type": "https://schema.org/object" + }, + "language": { + "@id": "ex:language", + "@type": "https://schema.org/object" + } + } + }, + "BankAccountCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#BankAccountCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "trustScores": { + "@id": "ex:trustScores" + }, + "nationalBankIdentifier": { + "@id": "ex:nationalBankIdentifier", + "@type": "https://schema.org/Text" + }, + "nationalBankAccountIdentifier": { + "@id": "ex:nationalBankAccountIdentifier", + "@type": "https://schema.org/Text" + }, + "internationalBankIdentifier": { + "@id": "ex:internationalBankIdentifier", + "@type": "https://schema.org/Text" + }, + "internationalBankAccountIdentifier": { + "@id": "ex:internationalBankAccountIdentifier", + "@type": "https://schema.org/Text" + }, + "currency": { + "@id": "ex:typeOf", + "@type": "https://schema.org/object" + } + } + }, + "AddressCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#AddressCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "version": { + "@id": "ex:typeOf", + "@type": "https://schema.org/object" + }, + "careOf": { + "@id": "ex:careOf", + "@type": "https://schema.org/Text" + }, + "contexts": { + "@id": "ex:contexts" + }, + "bpn": { + "@id": "ex:bpn" + }, + "country": { + "@id": "ex:country", + "@type": "https://schema.org/object" + }, + "administrativeAreas": { + "@id": "ex:administrativeAreas" + }, + "postCodes": { + "@id": "ex:postCodes" + }, + "localities": { + "@id": "ex:localities" + }, + "thoroughfares": { + "@id": "ex:thoroughfares" + }, + "premises": { + "@id": "ex:premises" + }, + "postalDeliveryPoints": { + "@id": "ex:postalDeliveryPoints" + }, + "geographicCoordinates": { + "@id": "ex:geographicCoordinates", + "@type": "https://schema.org/object" + }, + "types": { + "@id": "ex:types" + } + } + }, + "LegalFormCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#LegalFormCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "technicalKey": {"@id": "ex:technicalKey", "@type":"https://schema.org/Text"}, + "name": {"@id": "ex:name", "@type":"https://schema.org/Text"}, + "url": {"@id": "ex:url", "@type":"https://schema.org/Text"}, + "mainAbbreviation": {"@id": "https://schema.org/Text#4", "@type":"https://schema.org/Text"}, + "language": {"@id": "https://schema.org/Text#5", "@type":"https://schema.org/object"}, + "categories": {"@id": "https://schema.org/Text#6", "@type":"https://schema.org/ItemList"} + } + } + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/private-key.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/private-key.json new file mode 100644 index 000000000..4b9aa1c24 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/issuing/private-key.json @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "d": "RQenh0DD80AULwMqtTgYrihOft-kUGXGxL3prdtINDE9rp2ta3_CT1IcNUnDuG0F", + "crv": "P-384", + "x": "AqMfyYAh2SMf8bMoLbE6mOCbVyz8hukpBqrVheAFP4Anz2_cfzLEKKROD5EaAxSo", + "y": "P4KceKXv31JasLqvBPZWA9t1S2cMiHIQQ8ttAl5cFX3xBuzIPlgTRWPOVaNPWNFl" +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0001_vc.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0001_vc.json new file mode 100644 index 000000000..af27d5f5a --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0001_vc.json @@ -0,0 +1,27 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + }, + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0002_vc_forged.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0002_vc_forged.json new file mode 100644 index 000000000..82643a27e --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0002_vc_forged.json @@ -0,0 +1,36 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + }, + "id": "7f6c11b4-d2b9-43c3-8411-53f6089b5d2b", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": { + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-384", + "x": "eQbMauiHc9HuiqXT894gW5XTCrOpeY8cjLXAckfRtdVBLzVHKaiXAAxBFeVrSB75", + "y": "YOjxhMkdH9QnNmGCGuGXJrjAtk8CQ1kTmEEi9cg2R9ge-zh8SFT1Xu6awoUjK5Bv" + }, + "id": "https://org.eclipse.tractusx/keys/68c7189c-b849-4f85-b27d-c796c7cf29ed" + }, + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..xJWgUGQLLeC6XqZKXfkboY49NJeKW7GCOvqvXsP2iCXijMQVwz3yjCEf_4Hs3xLJZqz7_ZVYOEGeg5k2UMctVQ_uwsrPZ6w72jq4pMaNAlUIEeRDLYVUSl6v2FoeZftt" + }, + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0003_vc_embedded.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0003_vc_embedded.json new file mode 100644 index 000000000..0475a48a1 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0003_vc_embedded.json @@ -0,0 +1,37 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + }, + "id": "7f6c11b4-d2b9-43c3-8411-53f6089b5d2b", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": { + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-384", + "x": "eQbMauiHc9HuiqXT894gW5XTCrOpeY8cjLXAckfRtdVBLzVHKaiXAAxBFeVrSB75", + "y": "YOjxhMkdH9QnNmGCGuGXJrjAtk8CQ1kTmEEi9cg2R9ge-zh8SFT1Xu6awoUjK5Bv" + }, + "id": "https://org.eclipse.tractusx/keys/68c7189c-b849-4f85-b27d-c796c7cf29ed" + }, + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..0ueANOomarONwEL2Y0QnCFjgOdgPjI8kL2Wk4QWh8SJjvVTR80ASVh7bi8HlQp6dUigP3r509oMQkXB6TEddi0D8oQc2Lv0uWxl7yxPInBcfIsWmQrFBTb4mCSU_MJwE" + }, + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0004_vc_two_valid_proofs.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0004_vc_two_valid_proofs.json new file mode 100644 index 000000000..c68077de3 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0004_vc_two_valid_proofs.json @@ -0,0 +1,45 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": [ + { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": { + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "WNAhS5ptnuCCX0GAvlz_Ng0vLt72fddnn3kQTkQSYhU" + }, + "id": "https://org.eclipse.tractusx/keys/e4f14fcc-d607-487d-ac42-2f526eedc91a" + }, + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..UsYPNpv_vwcjQliZ5n5ZZECzE9S7u_vLYf2pFQgqdXCVAPdMQ3IpbEdOxe4xgi-hzw7iqpE8lRRwBGDCPOqqDA" + }, + { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + } + ], + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0005_vc_one_forged_proof.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0005_vc_one_forged_proof.json new file mode 100644 index 000000000..7df5f179a --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0005_vc_one_forged_proof.json @@ -0,0 +1,45 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": [ + { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": { + "type": "JsonWebKey2020", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + }, + "id": "https://org.eclipse.tractusx/keys/e4f14fcc-d607-487d-ac42-2f526eedc91a" + }, + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..UsYPNpv_vwcjQliZ5n5ZZECzE9S7u_vLYf2pFQgqdXCVAPdMQ3IpbEdOxe4xgi-hzw7iqpE8lRRwBGDCPOqqDA" + }, + { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + } + ], + "@context": [ + "https://org.eclipse.tractusx/businessPartnerData", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vc_did_key.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vc_did_key.json new file mode 100644 index 000000000..83394a24e --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vc_did_key.json @@ -0,0 +1,28 @@ +{ + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:key:zC2zU1wUHhYYX4CDwNwky9f5jtSvp5aQy5aNRQMHEdpK5xkJMy6TcMbWBP3scHbR6hhidR3RRjfAA7cuLxjydXgEiZUzRzguozYFeR3G6SzjAwswJ6hXKBWhFEHm2L6Rd6GRAw8r3kyPovxvcabdMF2gBy5TAioY1mVYFeT6", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..Om6PnvxKvcHXUwT6mycHMf8aPQADAlJKCP4TgCBPgd4-Y67DwcIvZkMZrPxcNPskUIKKFXr_1Ecv5mE016xBtS03itgUsZY7BjD44oMAYgyfw1t2Gdpcph-GvuB_9U0Q" + }, + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vp_compacted.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vp_compacted.json new file mode 100644 index 000000000..ec42acc49 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0006_vp_compacted.json @@ -0,0 +1,39 @@ +{ + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": "VerifiablePresentation", + "verifiableCredential": { + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + } + }, + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..qVCNVL_jxQdqa509KPTjRERopJiRtW1CqctVD_uGtUlCNF9oM2eB1L821YvjW0VjZjP6XdS5bLfQpG3azg9Hm8-L4vFBiH8HgEdVllHVcmO1odG-2GQAnhdP6Kdg42Wh" + }, + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0007_vp_compacted_forged.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0007_vp_compacted_forged.json new file mode 100644 index 000000000..30b8c8400 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/0007_vp_compacted_forged.json @@ -0,0 +1,39 @@ +{ + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": "VerifiablePresentation", + "verifiableCredential": { + "issuanceDate": "2023-06-12T13:13:30Z", + "credentialSubject": { + "http://schema.org/identifier": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential" + }, + "id": "https://org.eclipse.tractusx/testcases/t0001", + "type": [ + "VerifiableCredential", + "BpnCredentialCX" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T23:00:00Z", + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + } + }, + "sec:proof": { + "type": "JsonWebSignature2020", + "created": "2022-12-31T23:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://org.eclipse.tractusx/verification-method", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzM4NCJ9..dbi6LFkdeBeCz3sHaxRRFVJC2_rF8Z_oYqaoNOpYtzQh61WP78pK7nKT53WsE-7uiBUMamLA8vEGJpFQ3h4MXDi2OKh1YDpphS_pwyDkqYbsguMs2KYqPxe8t1OC2G1o" + }, + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://www.w3.org/ns/did/v1" + ] +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/businessPartnerData.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/businessPartnerData.json new file mode 100644 index 000000000..b36e7cd46 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/businessPartnerData.json @@ -0,0 +1,177 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "BpnCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#BpnCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "bpn": "http://schema.org/identifier" + } + }, + "MembershipCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#MembershipCredential", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "ex": "https://example.org/examples#", + "startTime": { + "@id": "https://schema.org/startTime", + "@type": "https://schema.org/DateTime" + }, + "memberOf": { + "@id": "https://schema.org/memberOf", + "@type": "https://schema.org/Text" + }, + "status": { + "@id": "ex:status", + "@type": "https://schema.org/Text" + } + } + }, + "NameCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#NameCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "value": { + "@id": "ex:value", + "@type": "https://schema.org/Text" + }, + "name": { + "@id": "ex:name", + "@type": "https://schema.org/Text" + }, + "shortName": { + "@id": "ex:shortName", + "@type": "https://schema.org/Text" + }, + "fipsCode": { + "@id": "ex:fipsCode", + "@type": "https://schema.org/Text" + }, + "number": { + "@id": "ex:number", + "@type": "https://schema.org/Text" + }, + "direction": { + "@id": "ex:direction", + "@type": "https://schema.org/Text" + }, + "nameType": { + "@id": "ex:nameType", + "@type": "https://schema.org/object" + }, + "language": { + "@id": "ex:language", + "@type": "https://schema.org/object" + } + } + }, + "BankAccountCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#BankAccountCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "trustScores": { + "@id": "ex:trustScores" + }, + "nationalBankIdentifier": { + "@id": "ex:nationalBankIdentifier", + "@type": "https://schema.org/Text" + }, + "nationalBankAccountIdentifier": { + "@id": "ex:nationalBankAccountIdentifier", + "@type": "https://schema.org/Text" + }, + "internationalBankIdentifier": { + "@id": "ex:internationalBankIdentifier", + "@type": "https://schema.org/Text" + }, + "internationalBankAccountIdentifier": { + "@id": "ex:internationalBankAccountIdentifier", + "@type": "https://schema.org/Text" + }, + "currency": { + "@id": "ex:typeOf", + "@type": "https://schema.org/object" + } + } + }, + "AddressCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#AddressCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "uuid": "http://schema.org/identifier", + "version": { + "@id": "ex:typeOf", + "@type": "https://schema.org/object" + }, + "careOf": { + "@id": "ex:careOf", + "@type": "https://schema.org/Text" + }, + "contexts": { + "@id": "ex:contexts" + }, + "bpn": { + "@id": "ex:bpn" + }, + "country": { + "@id": "ex:country", + "@type": "https://schema.org/object" + }, + "administrativeAreas": { + "@id": "ex:administrativeAreas" + }, + "postCodes": { + "@id": "ex:postCodes" + }, + "localities": { + "@id": "ex:localities" + }, + "thoroughfares": { + "@id": "ex:thoroughfares" + }, + "premises": { + "@id": "ex:premises" + }, + "postalDeliveryPoints": { + "@id": "ex:postalDeliveryPoints" + }, + "geographicCoordinates": { + "@id": "ex:geographicCoordinates", + "@type": "https://schema.org/object" + }, + "types": { + "@id": "ex:types" + } + } + }, + "LegalFormCredential": { + "@id": "https://org.eclipse.tractusx/businessPartnerData#LegalFormCredential", + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "technicalKey": {"@id": "ex:technicalKey", "@type":"https://schema.org/Text"}, + "name": {"@id": "ex:name", "@type":"https://schema.org/Text"}, + "url": {"@id": "ex:url", "@type":"https://schema.org/Text"}, + "mainAbbreviation": {"@id": "https://schema.org/Text#4", "@type":"https://schema.org/Text"}, + "language": {"@id": "https://schema.org/Text#5", "@type":"https://schema.org/object"}, + "categories": {"@id": "https://schema.org/Text#6", "@type":"https://schema.org/ItemList"} + } + } + } +} diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/verification-method.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/verification-method.json new file mode 100644 index 000000000..c541be484 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/jws2020/verifying/verification-method.json @@ -0,0 +1,11 @@ +{ + "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"], + "type": "JsonWebKey2020", + "id": "https://org.eclipse.tractusx/verification-keys.json", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-384", + "x": "AqMfyYAh2SMf8bMoLbE6mOCbVyz8hukpBqrVheAFP4Anz2_cfzLEKKROD5EaAxSo", + "y": "P4KceKXv31JasLqvBPZWA9t1S2cMiHIQQ8ttAl5cFX3xBuzIPlgTRWPOVaNPWNFl" + } +} \ No newline at end of file diff --git a/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/rsakey.json b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/rsakey.json new file mode 100644 index 000000000..aa8d9df69 --- /dev/null +++ b/edc-extensions/ssi/jws2020-crypto-suite/src/test/resources/rsakey.json @@ -0,0 +1,13 @@ +{ + "kty": "RSA", + "kid": "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df", + "use": "sig", + "n": "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w", + "e": "AQAB", + "d": "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q", + "p": "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0", + "q": "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8", + "dp": "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE", + "dq": "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk", + "qi": "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg" +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ce9e5778..76e09c1c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,9 @@ aws = "2.20.85" rsApi = "3.1.0" jupiter = "5.9.3" assertj = "3.24.2" +jakarta-json = "2.0.1" +tink = "1.7.0" +iron-vc = "0.8.1" [libraries] edc-spi-catalog = { module = "org.eclipse.edc:catalog-spi", version.ref = "edc" } @@ -34,12 +37,14 @@ edc-spi-aggregateservices = { module = "org.eclipse.edc:aggregate-service-spi", edc-spi-controlplane = { module = "org.eclipse.edc:control-plane-spi", version.ref = "edc" } edc-spi-web = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } edc-spi-http = { module = "org.eclipse.edc:http-spi", version.ref = "edc" } +edc-spi-jsonld = { module = "org.eclipse.edc:json-ld-spi", version.ref = "edc" } edc-spi-jwt = { module = "org.eclipse.edc:jwt-spi", version.ref = "edc" } edc-jwt-core = { module = "org.eclipse.edc:jwt-core", version.ref = "edc" } edc-spi-oauth2 = { module = "org.eclipse.edc:oauth2-spi", version.ref = "edc" } edc-util = { module = "org.eclipse.edc:util", version.ref = "edc" } edc-boot = { module = "org.eclipse.edc:boot", version.ref = "edc" } edc-config-filesystem = { module = "org.eclipse.edc:configuration-filesystem", version.ref = "edc" } +edc-jsonld = { module = "org.eclipse.edc:json-ld", version.ref = "edc" } edc-vault-filesystem = { module = "org.eclipse.edc:vault-filesystem", version.ref = "edc" } edc-core-controlplane = { module = "org.eclipse.edc:control-plane-core", version.ref = "edc" } edc-core-connector = { module = "org.eclipse.edc:connector-core", version.ref = "edc" } @@ -127,8 +132,11 @@ apache-sshd-sftp = { module = "org.apache.sshd:sshd-sftp", version.ref = "apache testcontainers-junit = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" } aws-s3 = { module = "software.amazon.awssdk:s3", version.ref = "aws" } jakarta-rsApi = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version.ref = "rsApi" } +jakartaJson = { module = "org.glassfish:jakarta.json", version.ref = "jakarta-json" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "jupiter" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } +tink = { module = "com.google.crypto.tink:tink", version.ref = "tink" } +apicatalog-iron-vc = { module = "com.apicatalog:iron-verifiable-credentials", version.ref = "iron-vc" } [bundles] edc-connector = ["edc.boot", "edc.core-connector", "edc.core-controlplane", "edc.api-observability"] diff --git a/settings.gradle.kts b/settings.gradle.kts index c4738535e..f6e3a94be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ include(":edc-extensions:control-plane-adapter-callback") include(":edc-extensions:edr-cache-sql") include("edc-extensions:ssi:ssi-identity-core") include("edc-extensions:ssi:ssi-miw-credential-client") +include("edc-extensions:ssi:jws2020-crypto-suite")