Skip to content

Commit 19554e9

Browse files
authored
feat: introduce encryption algo registry (#5423)
* feat: introduce encryption algo registry * chore: pr remarks
1 parent e9e71e7 commit 19554e9

File tree

19 files changed

+346
-121
lines changed

19 files changed

+346
-121
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
plugins {
16+
`java-library`
17+
}
18+
19+
dependencies {
20+
api(project(":spi:common:encryption-spi"))
21+
}
22+
23+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.encryption;
16+
17+
import org.eclipse.edc.spi.result.Result;
18+
19+
import java.util.Map;
20+
import java.util.Optional;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
23+
public class EncryptionAlgorithmRegistryImpl implements EncryptionAlgorithmRegistry {
24+
25+
private final Map<String, EncryptionAlgorithm> algorithms = new ConcurrentHashMap<>();
26+
27+
private final boolean failOnUnsupported;
28+
29+
public EncryptionAlgorithmRegistryImpl(boolean failOnUnsupported) {
30+
this.failOnUnsupported = failOnUnsupported;
31+
}
32+
33+
@Override
34+
public void register(String algorithm, EncryptionAlgorithm service) {
35+
algorithms.put(algorithm, service);
36+
}
37+
38+
@Override
39+
public boolean supports(String algorithm) {
40+
return algorithms.containsKey(algorithm);
41+
}
42+
43+
@Override
44+
public Result<String> encrypt(String algorithm, String plainText) {
45+
return Optional.ofNullable(algorithms.get(algorithm))
46+
.map(encryptionAlgorithm -> encryptionAlgorithm.encrypt(plainText))
47+
.orElseGet(() -> unsupportedAlgorithm(plainText, algorithm));
48+
}
49+
50+
@Override
51+
public Result<String> decrypt(String algorithm, String cipherText) {
52+
return Optional.ofNullable(algorithms.get(algorithm))
53+
.map(encryptionAlgorithm -> encryptionAlgorithm.decrypt(cipherText))
54+
.orElseGet(() -> unsupportedAlgorithm(cipherText, algorithm));
55+
}
56+
57+
private Result<String> unsupportedAlgorithm(String text, String algorithm) {
58+
if (failOnUnsupported) {
59+
return Result.failure("Unsupported encryption algorithm: " + algorithm);
60+
} else {
61+
return Result.success(text);
62+
}
63+
}
64+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2025 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.encryption;
16+
17+
import org.eclipse.edc.spi.result.Result;
18+
import org.junit.jupiter.api.Test;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.mockito.ArgumentMatchers.anyString;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.when;
26+
27+
class EncryptionAlgorithmRegistryImplTest {
28+
29+
private final EncryptionAlgorithm algorithm = mock();
30+
31+
@Test
32+
void registerAndSupports() {
33+
var registry = new EncryptionAlgorithmRegistryImpl(true);
34+
assertFalse(registry.supports("echo"));
35+
36+
registry.register("echo", algorithm);
37+
assertTrue(registry.supports("echo"));
38+
}
39+
40+
@Test
41+
void encryptAndDecryptWithRegisteredAlgorithm() {
42+
var registry = new EncryptionAlgorithmRegistryImpl(true);
43+
when(algorithm.encrypt(anyString())).then(a -> Result.success("enc:" + a.getArgument(0)));
44+
when(algorithm.decrypt(anyString())).then(a -> Result.success("dec:" + a.getArgument(0)));
45+
registry.register("echo", algorithm);
46+
47+
Result<String> encryptResult = registry.encrypt("echo", "hello");
48+
assertTrue(encryptResult.succeeded());
49+
assertEquals("enc:hello", encryptResult.getContent());
50+
51+
Result<String> decryptResult = registry.decrypt("echo", "ciph");
52+
assertTrue(decryptResult.succeeded());
53+
assertEquals("dec:ciph", decryptResult.getContent());
54+
}
55+
56+
@Test
57+
void unsupportedAlgorithm_whenFailOnUnsupported_true_returnsFailure() {
58+
var registry = new EncryptionAlgorithmRegistryImpl(true);
59+
60+
Result<String> res = registry.encrypt("unknown", "plain");
61+
assertFalse(res.succeeded());
62+
}
63+
64+
@Test
65+
void unsupportedAlgorithm_whenFailOnUnsupported_false_returnsPlainText() {
66+
var registry = new EncryptionAlgorithmRegistryImpl(false);
67+
68+
Result<String> res = registry.encrypt("unknown", "plain");
69+
assertTrue(res.succeeded());
70+
assertEquals("plain", res.getContent());
71+
}
72+
}

core/common/participant-context-config-core/src/main/java/org/eclipse/edc/participantcontext/config/ParticipantContextConfigImpl.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
package org.eclipse.edc.participantcontext.config;
1616

17-
import org.eclipse.edc.encryption.EncryptionService;
17+
import org.eclipse.edc.encryption.EncryptionAlgorithmRegistry;
1818
import org.eclipse.edc.participantcontext.spi.config.ParticipantContextConfig;
1919
import org.eclipse.edc.participantcontext.spi.config.model.ParticipantContextConfiguration;
2020
import org.eclipse.edc.participantcontext.spi.config.store.ParticipantContextConfigStore;
@@ -31,13 +31,15 @@
3131
public class ParticipantContextConfigImpl implements ParticipantContextConfig {
3232

3333

34-
private final EncryptionService encryptionService;
34+
private final EncryptionAlgorithmRegistry registry;
35+
private final String encryptionAlgorithm;
3536
private final ParticipantContextConfigStore configStore;
3637
private final TransactionContext transactionContext;
3738

3839

39-
public ParticipantContextConfigImpl(EncryptionService encryptionService, ParticipantContextConfigStore configStore, TransactionContext transactionContext) {
40-
this.encryptionService = encryptionService;
40+
public ParticipantContextConfigImpl(EncryptionAlgorithmRegistry registry, String encryptionAlgorithm, ParticipantContextConfigStore configStore, TransactionContext transactionContext) {
41+
this.registry = registry;
42+
this.encryptionAlgorithm = encryptionAlgorithm;
4143
this.configStore = configStore;
4244
this.transactionContext = transactionContext;
4345
}
@@ -89,7 +91,7 @@ public String getSensitiveString(String participantContextId, String key) {
8991
if (encryptedValue == null) {
9092
return null;
9193
}
92-
return encryptionService.decrypt(encryptedValue)
94+
return registry.decrypt(encryptionAlgorithm, encryptedValue)
9395
.orElseThrow(f -> new EdcException(format("Failed to decrypt sensitive config value for key %s and participant context %s", key, participantContextId)));
9496
}
9597

core/common/participant-context-config-core/src/main/java/org/eclipse/edc/participantcontext/config/ParticipantContextConfigServicesExtension.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,53 @@
1414

1515
package org.eclipse.edc.participantcontext.config;
1616

17-
import org.eclipse.edc.encryption.EncryptionService;
17+
import org.eclipse.edc.encryption.EncryptionAlgorithmRegistry;
1818
import org.eclipse.edc.participantcontext.config.service.ParticipantContextConfigServiceImpl;
1919
import org.eclipse.edc.participantcontext.spi.config.ParticipantContextConfig;
2020
import org.eclipse.edc.participantcontext.spi.config.service.ParticipantContextConfigService;
2121
import org.eclipse.edc.participantcontext.spi.config.store.ParticipantContextConfigStore;
2222
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
2323
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
2424
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
25+
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
26+
import org.eclipse.edc.spi.monitor.Monitor;
2527
import org.eclipse.edc.spi.system.ServiceExtension;
2628
import org.eclipse.edc.transaction.spi.TransactionContext;
2729

28-
import static org.eclipse.edc.participantcontext.config.defaults.ParticipantContextConfigDefaultServicesExtension.NAME;
30+
import static org.eclipse.edc.participantcontext.config.ParticipantContextConfigServicesExtension.NAME;
2931

3032
@Extension(NAME)
3133
public class ParticipantContextConfigServicesExtension implements ServiceExtension {
3234

3335
public static final String NAME = "Participant Context Config Services Extension";
3436

37+
@Setting(
38+
description = "The encryption algorithm used for encrypting and decrypting sensitive config.",
39+
key = "edc.participants.config.encryption.algorithm",
40+
defaultValue = "aes"
41+
)
42+
private String encryptionAlgorithm;
43+
3544
@Inject
3645
private ParticipantContextConfigStore configStore;
3746

3847
@Inject
3948
private TransactionContext transactionContext;
4049

4150
@Inject
42-
private EncryptionService encryptionService;
51+
private EncryptionAlgorithmRegistry encryptionRegistry;
52+
53+
@Inject
54+
private Monitor monitor;
4355

4456
@Provider
4557
public ParticipantContextConfigService participantContextConfigService() {
46-
return new ParticipantContextConfigServiceImpl(encryptionService, configStore, transactionContext);
58+
return new ParticipantContextConfigServiceImpl(encryptionRegistry, encryptionAlgorithm, configStore, transactionContext);
4759
}
4860

4961
@Provider
5062
public ParticipantContextConfig participantContextConfig() {
51-
return new ParticipantContextConfigImpl(encryptionService, configStore, transactionContext);
63+
return new ParticipantContextConfigImpl(encryptionRegistry, encryptionAlgorithm, configStore, transactionContext);
5264
}
5365

5466
}

core/common/participant-context-config-core/src/main/java/org/eclipse/edc/participantcontext/config/service/ParticipantContextConfigServiceImpl.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
package org.eclipse.edc.participantcontext.config.service;
1616

17-
import org.eclipse.edc.encryption.EncryptionService;
17+
import org.eclipse.edc.encryption.EncryptionAlgorithmRegistry;
1818
import org.eclipse.edc.participantcontext.spi.config.model.ParticipantContextConfiguration;
1919
import org.eclipse.edc.participantcontext.spi.config.service.ParticipantContextConfigService;
2020
import org.eclipse.edc.participantcontext.spi.config.store.ParticipantContextConfigStore;
@@ -28,12 +28,14 @@
2828

2929
public class ParticipantContextConfigServiceImpl implements ParticipantContextConfigService {
3030

31-
private final EncryptionService encryptionService;
31+
private final EncryptionAlgorithmRegistry encryptionRegistry;
32+
private final String encryptionAlgorithm;
3233
private final ParticipantContextConfigStore configStore;
3334
private final TransactionContext transactionContext;
3435

35-
public ParticipantContextConfigServiceImpl(EncryptionService encryptionService, ParticipantContextConfigStore configStore, TransactionContext transactionContext) {
36-
this.encryptionService = encryptionService;
36+
public ParticipantContextConfigServiceImpl(EncryptionAlgorithmRegistry encryptionRegistry, String encryptionAlgorithm, ParticipantContextConfigStore configStore, TransactionContext transactionContext) {
37+
this.encryptionRegistry = encryptionRegistry;
38+
this.encryptionAlgorithm = encryptionAlgorithm;
3739
this.configStore = configStore;
3840
this.transactionContext = transactionContext;
3941
}
@@ -68,7 +70,7 @@ private Result<ParticipantContextConfiguration> encryptEntries(ParticipantContex
6870
}
6971

7072
private Result<Map.Entry<String, String>> encryptEntryMap(Map.Entry<String, String> entry) {
71-
return encryptionService.encrypt(entry.getValue())
73+
return encryptionRegistry.encrypt(encryptionAlgorithm, entry.getValue())
7274
.map(encrypted -> Map.entry(entry.getKey(), encrypted));
7375
}
7476

core/common/participant-context-config-core/src/test/java/org/eclipse/edc/participantcontext/config/ParticipantContextConfigImplTest.java

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
package org.eclipse.edc.participantcontext.config;
1616

17-
import org.eclipse.edc.encryption.EncryptionService;
17+
import org.eclipse.edc.encryption.EncryptionAlgorithmRegistry;
1818
import org.eclipse.edc.participantcontext.spi.config.ParticipantContextConfig;
1919
import org.eclipse.edc.participantcontext.spi.config.model.ParticipantContextConfiguration;
2020
import org.eclipse.edc.participantcontext.spi.config.store.ParticipantContextConfigStore;
@@ -43,8 +43,8 @@ public class ParticipantContextConfigImplTest {
4343

4444
private static final String PARTICIPANT_CONTEXT_ID = "participantContextId";
4545
private final ParticipantContextConfigStore store = mock();
46-
private final EncryptionService encryptionService = mock();
47-
private final ParticipantContextConfig contextConfig = new ParticipantContextConfigImpl(encryptionService, store, new NoopTransactionContext());
46+
private final EncryptionAlgorithmRegistry registry = mock();
47+
private final ParticipantContextConfig contextConfig = new ParticipantContextConfigImpl(registry, "any", store, new NoopTransactionContext());
4848

4949
@ParameterizedTest
5050
@ArgumentsSource(SettingProvider.class)
@@ -88,40 +88,6 @@ void notFound(SettingCall setting, String key, String value, Object expectedValu
8888

8989
}
9090

91-
@Nested
92-
class GetSensitiveString {
93-
94-
@Test
95-
void shouldGetPrivateSetting() {
96-
var cfg = ParticipantContextConfiguration.Builder.newInstance().participantContextId(PARTICIPANT_CONTEXT_ID)
97-
.entries(Map.of("key", "value"))
98-
.privateEntries(Map.of("private.key", "encryptedValue"))
99-
.build();
100-
101-
when(encryptionService.decrypt("encryptedValue")).thenReturn(Result.success("decryptedValue"));
102-
when(store.get(PARTICIPANT_CONTEXT_ID)).thenReturn(cfg);
103-
104-
var result = contextConfig.getSensitiveString(PARTICIPANT_CONTEXT_ID, "private.key");
105-
106-
assertThat(result).isNotNull()
107-
.isEqualTo("decryptedValue");
108-
}
109-
110-
@Test
111-
void shouldReturnNull_whenNoSettingFound() {
112-
var cfg = ParticipantContextConfiguration.Builder.newInstance().participantContextId(PARTICIPANT_CONTEXT_ID)
113-
.entries(emptyMap())
114-
.privateEntries(emptyMap())
115-
.build();
116-
when(store.get(PARTICIPANT_CONTEXT_ID)).thenReturn(cfg);
117-
118-
var result = contextConfig.getSensitiveString(PARTICIPANT_CONTEXT_ID, "any");
119-
120-
assertThat(result).isNull();
121-
verifyNoInteractions(encryptionService);
122-
}
123-
}
124-
12591
@FunctionalInterface
12692
private interface SettingCall {
12793
Object call(ParticipantContextConfig service, String participantContextId, String key);
@@ -167,4 +133,38 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
167133
);
168134
}
169135
}
136+
137+
@Nested
138+
class GetSensitiveString {
139+
140+
@Test
141+
void shouldGetPrivateSetting() {
142+
var cfg = ParticipantContextConfiguration.Builder.newInstance().participantContextId(PARTICIPANT_CONTEXT_ID)
143+
.entries(Map.of("key", "value"))
144+
.privateEntries(Map.of("private.key", "encryptedValue"))
145+
.build();
146+
147+
when(registry.decrypt("any", "encryptedValue")).thenReturn(Result.success("decryptedValue"));
148+
when(store.get(PARTICIPANT_CONTEXT_ID)).thenReturn(cfg);
149+
150+
var result = contextConfig.getSensitiveString(PARTICIPANT_CONTEXT_ID, "private.key");
151+
152+
assertThat(result).isNotNull()
153+
.isEqualTo("decryptedValue");
154+
}
155+
156+
@Test
157+
void shouldReturnNull_whenNoSettingFound() {
158+
var cfg = ParticipantContextConfiguration.Builder.newInstance().participantContextId(PARTICIPANT_CONTEXT_ID)
159+
.entries(emptyMap())
160+
.privateEntries(emptyMap())
161+
.build();
162+
when(store.get(PARTICIPANT_CONTEXT_ID)).thenReturn(cfg);
163+
164+
var result = contextConfig.getSensitiveString(PARTICIPANT_CONTEXT_ID, "any");
165+
166+
assertThat(result).isNull();
167+
verifyNoInteractions(registry);
168+
}
169+
}
170170
}

0 commit comments

Comments
 (0)