Skip to content

Commit

Permalink
feat: supports verification method id as URL
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood committed Jan 23, 2024
1 parent 185b633 commit cd3bae9
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 25 deletions.
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

0 comments on commit cd3bae9

Please sign in to comment.