Skip to content

Commit

Permalink
feat: add KeyPairService (#237)
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger authored Jan 24, 2024
1 parent 8dcf992 commit 3928c05
Show file tree
Hide file tree
Showing 10 changed files with 572 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import com.apicatalog.ld.signature.SignatureSuite;
import org.eclipse.edc.identityhub.defaults.EdcScopeToCriterionTransformer;
import org.eclipse.edc.identityhub.defaults.InMemoryCredentialStore;
import org.eclipse.edc.identityhub.defaults.InMemoryKeyPairResourceStore;
import org.eclipse.edc.identityhub.defaults.InMemoryParticipantContextStore;
import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer;
import org.eclipse.edc.identityhub.spi.model.IdentityHubConstants;
import org.eclipse.edc.identityhub.spi.store.CredentialStore;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.identityhub.token.rules.ClaimIsPresentRule;
import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry;
Expand Down Expand Up @@ -75,6 +77,11 @@ public ParticipantContextStore createDefaultParticipantContextStore() {
return new InMemoryParticipantContextStore();
}

@Provider(isDefault = true)
public KeyPairResourceStore createDefaultKeyPairResourceStore() {
return new InMemoryKeyPairResourceStore();
}

@Provider(isDefault = true)
public ScopeToCriterionTransformer createScopeTransformer(ServiceExtensionContext context) {
context.getMonitor().warning("Using the default EdcScopeToCriterionTransformer. This is not intended for production use and should be replaced " +
Expand Down
27 changes: 27 additions & 0 deletions core/identity-hub-keypairs/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

plugins {
`java-library`
}

dependencies {
api(project(":spi:identity-hub-spi"))
api(project(":spi:identity-hub-store-spi"))
api(libs.edc.spi.transaction)
implementation(project(":extensions:common:security"))
implementation(libs.edc.common.crypto)
implementation(libs.edc.core.connector)
testImplementation(libs.edc.junit)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.keypairs;

import org.eclipse.edc.identityhub.spi.KeyPairService;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.security.Vault;
import org.eclipse.edc.spi.system.ServiceExtension;

import static org.eclipse.edc.identityhub.keypairs.KeyPairServiceExtension.NAME;

@Extension(NAME)
public class KeyPairServiceExtension implements ServiceExtension {
public static final String NAME = "KeyPair Service Extension";

@Inject
private Vault vault;
@Inject
private KeyPairResourceStore keyPairResourceStore;

@Provider
public KeyPairService createParticipantService() {
return new KeyPairServiceImpl(keyPairResourceStore, vault);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.identityhub.keypairs;

import org.eclipse.edc.identityhub.security.KeyPairGenerator;
import org.eclipse.edc.identityhub.spi.KeyPairService;
import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor;
import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore;
import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource;
import org.eclipse.edc.identityhub.spi.store.model.KeyPairState;
import org.eclipse.edc.security.token.jwt.CryptoConverter;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.security.Vault;
import org.jetbrains.annotations.Nullable;

import java.time.Instant;
import java.util.Objects;
import java.util.Optional;

public class KeyPairServiceImpl implements KeyPairService {
private final KeyPairResourceStore keyPairResourceStore;
private final Vault vault;

public KeyPairServiceImpl(KeyPairResourceStore keyPairResourceStore, Vault vault) {
this.keyPairResourceStore = keyPairResourceStore;
this.vault = vault;
}

@Override
public ServiceResult<Void> addKeyPair(String participantId, KeyDescriptor keyDescriptor, boolean makeDefault) {

var key = generateOrGetKey(keyDescriptor);
if (key.failed()) {
return ServiceResult.badRequest(key.getFailureDetail());
}

var newResource = KeyPairResource.Builder.newInstance()
.keyId(keyDescriptor.getKeyId())
.state(KeyPairState.CREATED)
.isDefaultPair(makeDefault)
.privateKeyAlias(keyDescriptor.getPrivateKeyAlias())
.serializedPublicKey(key.getContent())
.timestamp(Instant.now().toEpochMilli())
.participantId(participantId)
.build();

return ServiceResult.from(keyPairResourceStore.create(newResource));
}

@Override
public ServiceResult<Void> rotateKeyPair(String oldId, KeyDescriptor newKeySpec, long duration) {
Objects.requireNonNull(newKeySpec);
var oldKey = findById(oldId);
if (oldKey == null) {
return ServiceResult.notFound("A KeyPairResource with ID '%s' does not exist.".formatted(oldId));
}

var participantId = oldKey.getParticipantId();
boolean wasDefault = oldKey.isDefaultPair();

// deactivate the old key
var oldAlias = oldKey.getPrivateKeyAlias();
vault.deleteSecret(oldAlias);
oldKey.rotate(duration);
keyPairResourceStore.update(oldKey);

return addKeyPair(participantId, newKeySpec, wasDefault);
}

@Override
public ServiceResult<Void> revokeKey(String id, @Nullable KeyDescriptor newKeySpec) {
var oldKey = findById(id);
if (oldKey == null) {
return ServiceResult.notFound("A KeyPairResource with ID '%s' does not exist.".formatted(id));
}

var participantId = oldKey.getParticipantId();
boolean wasDefault = oldKey.isDefaultPair();

// deactivate the old key
var oldAlias = oldKey.getPrivateKeyAlias();
vault.deleteSecret(oldAlias);
oldKey.revoke();
keyPairResourceStore.update(oldKey);
//todo: emit event for the did service, which should update the did document

if (newKeySpec != null) {
return addKeyPair(participantId, newKeySpec, wasDefault);
}
return ServiceResult.success();
}

private KeyPairResource findById(String oldId) {
var q = QuerySpec.Builder.newInstance()
.filter(new Criterion("id", "=", oldId)).build();
return keyPairResourceStore.query(q).map(list -> list.stream().findFirst().orElse(null)).orElse(f -> null);
}

private Result<String> generateOrGetKey(KeyDescriptor keyDescriptor) {
String publicKeySerialized;
if (keyDescriptor.getKeyGeneratorParams() != null) {
var keyPair = KeyPairGenerator.generateKeyPair(keyDescriptor.getKeyGeneratorParams());
if (keyPair.failed()) {
return keyPair.mapTo();
}
var privateJwk = CryptoConverter.createJwk(keyPair.getContent());
publicKeySerialized = privateJwk.toPublicJWK().toJSONString();
vault.storeSecret(keyDescriptor.getPrivateKeyAlias(), privateJwk.toJSONString());
} else {
// either take the public key from the JWK structure or the PEM field
publicKeySerialized = Optional.ofNullable(keyDescriptor.getPublicKeyJwk())
.map(m -> CryptoConverter.create(m).toJSONString())
.orElseGet(keyDescriptor::getPublicKeyPem);
}
return Result.success(publicKeySerialized);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#
# 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
#
#
org.eclipse.edc.identityhub.keypairs.KeyPairServiceExtension
Loading

0 comments on commit 3928c05

Please sign in to comment.