Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supports verification method id as URL #3795

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.jetbrains.annotations.Nullable;

import java.text.ParseException;
import java.util.HashMap;
import java.util.function.Function;
import java.util.regex.Pattern;

import static org.eclipse.edc.iam.did.spi.document.DidConstants.ALLOWED_VERIFICATION_TYPES;
Expand Down Expand Up @@ -58,10 +60,10 @@ protected Result<String> resolveInternal(String id) {
if (matcher.groupCount() > 1) {
key = matcher.group(GROUP_FRAGMENT);
}
return resolveDidPublicKey(did, key);
return resolveDidPublicKey(did, id, key);
}

private Result<String> resolveDidPublicKey(String didUrl, @Nullable String keyId) {
private Result<String> resolveDidPublicKey(String didUrl, String verificationMethodUrl, @Nullable String keyId) {
var didResult = resolverRegistry.resolve(didUrl);
if (didResult.failed()) {
return didResult.mapTo();
Expand All @@ -76,7 +78,7 @@ private Result<String> resolveDidPublicKey(String didUrl, @Nullable String keyId
.toList();

// if there are more than 1 verification methods with the same ID
if (verificationMethods.stream().map(VerificationMethod::getId).distinct().count() != verificationMethods.size()) {
if (verificationMethods.stream().map(verificationMethodIdMapper(didUrl)).distinct().count() != verificationMethods.size()) {
return Result.failure("Every verification method must have a unique ID");
}
Result<VerificationMethod> verificationMethod;
Expand All @@ -86,14 +88,14 @@ private Result<String> resolveDidPublicKey(String didUrl, @Nullable String keyId
return Result.failure("The key ID ('kid') is mandatory if DID contains >1 verification methods.");
}
verificationMethod = Result.from(verificationMethods.stream().findFirst());
} else { // look up VerificationMEthods by key ID
verificationMethod = verificationMethods.stream().filter(vm -> vm.getId().equals(keyId))
} else { // look up VerificationMethods by key ID or didUrl + key ID
verificationMethod = verificationMethods.stream().filter(vm -> vm.getId().equals(keyId) || vm.getId().equals(verificationMethodUrl))
.findFirst()
.map(Result::success)
.orElseGet(() -> Result.failure("No verification method found with key ID '%s'".formatted(keyId)));
}
return verificationMethod.compose(vm -> {
var key = vm.getPublicKeyJwk();
var key = new HashMap<>(vm.getPublicKeyJwk());
key.put(JWKParameterNames.KEY_ID, vm.getId());
try {
return Result.success(JWK.parse(key).toJSONString());
Expand All @@ -103,4 +105,15 @@ private Result<String> resolveDidPublicKey(String didUrl, @Nullable String keyId

});
}

// If the verification method id is relative uri we map it to didUrl + id
private Function<VerificationMethod, String> verificationMethodIdMapper(String didUrl) {
return (vm) -> {
if (vm.getId().startsWith(didUrl)) {
return vm.getId();
} else {
return didUrl + vm.getId();
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ class DidPublicKeyResolverImplTest {
private final DidResolverRegistry resolverRegistry = mock();
private final KeyParserRegistry keyParserRegistry = mock();
private final DidPublicKeyResolverImpl resolver = new DidPublicKeyResolverImpl(keyParserRegistry, resolverRegistry, mock(), mock());
private DidDocument didDocument;

public static String readFile(String filename) throws IOException {
try (var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)) {
Expand All @@ -58,25 +57,58 @@ public static String readFile(String filename) throws IOException {
}

@BeforeEach
public void setUp() throws JOSEException, IOException {
var eckey = (ECKey) ECKey.parseFromPEMEncodedObjects(readFile("public_secp256k1.pem"));
public void setUp() throws JOSEException {

var vm = VerificationMethod.Builder.newInstance()
.id(KEYID)
when(keyParserRegistry.parse(anyString())).thenReturn(Result.success(new ECKeyGenerator(Curve.P_256).generate().toPublicKey()));
}

private DidDocument createDidDocument(String verificationMethodId) {
return createDidDocumentBuilder(verificationMethodId).build();
}

private DidDocument createDidDocument() {
return createDidDocumentBuilder(KEYID).build();
}

private VerificationMethod createVerificationMethod(String verificationMethodId, ECKey eckey) {
return VerificationMethod.Builder.newInstance()
.id(verificationMethodId)
.type(DidConstants.ECDSA_SECP_256_K_1_VERIFICATION_KEY_2019)
.publicKeyJwk(eckey.toPublicJWK().toJSONObject())
.build();
}

didDocument = DidDocument.Builder.newInstance()
.verificationMethod(List.of(vm))
.service(Collections.singletonList(new Service("#my-service1", "MyService", "http://doesnotexi.st")))
.build();
private VerificationMethod createVerificationMethod(String verificationMethodId) {
try {
var eckey = (ECKey) ECKey.parseFromPEMEncodedObjects(readFile("public_secp256k1.pem"));
return createVerificationMethod(verificationMethodId, eckey);
} catch (JOSEException | IOException e) {
throw new RuntimeException(e);
}

when(keyParserRegistry.parse(anyString())).thenReturn(Result.success(new ECKeyGenerator(Curve.P_256).generate().toPublicKey()));
}

private DidDocument.Builder createDidDocumentBuilder(String verificationMethodId) {
var vm = createVerificationMethod(verificationMethodId);
return DidDocument.Builder.newInstance()
.verificationMethod(List.of(vm))
.service(Collections.singletonList(new Service("#my-service1", "MyService", "http://doesnotexi.st")));
}

@Test
void resolve() {
var didDocument = createDidDocument();
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL + KEYID);

assertThat(result).isSucceeded().isNotNull();
verify(resolverRegistry).resolve(DID_URL);
}

@Test
void resolve_withVerificationMethodUrlAsId() throws IOException, JOSEException {
var didDocument = createDidDocument(DID_URL + KEYID);
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL + KEYID);
Expand All @@ -97,6 +129,7 @@ void resolve_didNotFound() {

@Test
void resolve_didDoesNotContainPublicKey() {
var didDocument = createDidDocument();
didDocument.getVerificationMethod().clear();
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

Expand All @@ -108,11 +141,24 @@ void resolve_didDoesNotContainPublicKey() {

@Test
void resolve_didContainsMultipleKeysWithSameKeyId() throws JOSEException, IOException {
var publicKey = (ECKey) ECKey.parseFromPEMEncodedObjects(readFile("public_secp256k1.pem"));
var vm = VerificationMethod.Builder.newInstance().id(KEYID).type(DidConstants.JSON_WEB_KEY_2020).controller("")
.publicKeyJwk(publicKey.toJSONObject())
.build();
didDocument.getVerificationMethod().add(vm);
var vm = createVerificationMethod(KEYID);
var vm1 = createVerificationMethod(KEYID);
var didDocument = createDidDocumentBuilder(KEYID).verificationMethod(List.of(vm, vm1)).build();
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL + KEYID);

assertThat(result).isFailed()
.detail().contains("Every verification method must have a unique ID");
verify(resolverRegistry).resolve(DID_URL);
}

@Test
void resolve_didContainsMultipleKeysWithSameKeyId_withRelativeAndFullUrl() {
var vm = createVerificationMethod(DID_URL + KEYID);
var vm1 = createVerificationMethod(KEYID);

var didDocument = createDidDocumentBuilder(KEYID).verificationMethod(List.of(vm, vm1)).build();
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL + KEYID);
Expand All @@ -124,22 +170,25 @@ void resolve_didContainsMultipleKeysWithSameKeyId() throws JOSEException, IOExce

@Test
void resolve_publicKeyNotInPemFormat() {
didDocument.getVerificationMethod().clear();
var vm = VerificationMethod.Builder.newInstance().id("second-key").type(DidConstants.ECDSA_SECP_256_K_1_VERIFICATION_KEY_2019).controller("")
var secondKeyId = "#second-key";
var vm = VerificationMethod.Builder.newInstance().id(secondKeyId).type(DidConstants.ECDSA_SECP_256_K_1_VERIFICATION_KEY_2019).controller("")
.publicKeyJwk(Map.of("kty", "EC"))
.build();
didDocument.getVerificationMethod().add(vm);
var vm1 = createVerificationMethod(KEYID);

var didDocument = createDidDocumentBuilder(KEYID).verificationMethod(List.of(vm, vm1)).build();

when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL + KEYID);
var result = resolver.resolveKey(DID_URL + secondKeyId);

assertThat(result).isFailed();
verify(resolverRegistry).resolve(DID_URL);
}

@Test
void resolve_keyIdNullMultipleKeys() throws JOSEException, IOException {
var didDocument = createDidDocument();
var publicKey = (ECKey) ECKey.parseFromPEMEncodedObjects(readFile("public_secp256k1.pem"));
var vm = VerificationMethod.Builder.newInstance().id("#my-key2").type(DidConstants.JSON_WEB_KEY_2020).controller("")
.publicKeyJwk(publicKey.toJSONObject())
Expand All @@ -154,6 +203,7 @@ void resolve_keyIdNullMultipleKeys() throws JOSEException, IOException {

@Test
void resolve_keyIdIsNull_onlyOneVerificationMethod() {
var didDocument = createDidDocument();
when(resolverRegistry.resolve(DID_URL)).thenReturn(Result.success(didDocument));

var result = resolver.resolveKey(DID_URL);
Expand Down
Loading