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

Compose new map-and-encrypt functions #39

Merged
merged 3 commits into from
Mar 14, 2024
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 @@ -2,6 +2,7 @@

import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.TransformDirection;
import no.ssb.dlp.pseudo.core.PseudoException;
import no.ssb.dlp.pseudo.core.PseudoKeyset;
import no.ssb.dlp.pseudo.core.PseudoOperation;
Expand Down Expand Up @@ -41,7 +42,7 @@ public Optional<PseudoFuncRuleMatch> match(FieldDescriptor field) {
public void init(FieldDescriptor field, String varValue) {
Optional<PseudoFuncRuleMatch> match = pseudoFuncs.findPseudoFunc(field);
if (match.isPresent()) {
match.get().getFunc().init(PseudoFuncInput.of(varValue));
match.get().getFunc().init(PseudoFuncInput.of(varValue), TransformDirection.APPLY);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.extern.slf4j.Slf4j;
import no.ssb.crypto.tink.fpe.UnknownCharacterStrategy;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFunc;
import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.fpe.Alphabets;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFunc;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFuncConfig;
Expand Down Expand Up @@ -35,6 +37,8 @@ class PseudoFuncConfigFactory {
tinkDaeadPseudoFuncConfigPreset(DAEAD),
tinkFpePseudoFuncConfigPreset(FF31),
sidMappingPseudoFuncConfigPreset(MAP_SID),
sidMappingAndTinkFpePseudoFuncConfigPreset(MAP_SID_FF31),
sidMappingAndTinkDaeadPseudoFuncConfigPreset(MAP_SID_DAEAD),
redactPseudoFuncConfigPreset(REDACT),
fpePseudoFuncConfigPreset(FPE + "-text", alphabetNameOf(ALPHANUMERIC, WHITESPACE, SYMBOLS)),
fpePseudoFuncConfigPreset(FPE + "-text_no", alphabetNameOf(ALPHANUMERIC_NO, WHITESPACE, SYMBOLS)),
Expand All @@ -53,6 +57,27 @@ private static PseudoFuncConfigPreset sidMappingPseudoFuncConfigPreset(String fu
.build();
}

private static PseudoFuncConfigPreset sidMappingAndTinkFpePseudoFuncConfigPreset(String funcName) {
return PseudoFuncConfigPreset.builder(funcName, MapAndEncryptFunc.class)
.staticParam(MapAndEncryptFuncConfig.Param.MAP_FUNC_IMPL, MapFunc.class.getName())
.staticParam(MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, TinkFpeFunc.class.getName())
.requiredParam(String.class, TinkFpeFuncConfig.Param.KEY_ID)
.optionalParam(String.class, MapFuncConfig.Param.SNAPSHOT_DATE)
.optionalParam(UnknownCharacterStrategy.class, TinkFpeFuncConfig.Param.UNKNOWN_CHARACTER_STRATEGY, UnknownCharacterStrategy.FAIL)
.optionalParam(String.class, TinkFpeFuncConfig.Param.TWEAK)
.optionalParam(Character.class, TinkFpeFuncConfig.Param.REDACT_CHAR)
.build();
}

private static PseudoFuncConfigPreset sidMappingAndTinkDaeadPseudoFuncConfigPreset(String funcName) {
return PseudoFuncConfigPreset.builder(funcName, MapAndEncryptFunc.class)
.staticParam(MapAndEncryptFuncConfig.Param.MAP_FUNC_IMPL, MapFunc.class.getName())
.staticParam(MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, TinkDaeadFunc.class.getName())
.optionalParam(String.class, MapFuncConfig.Param.SNAPSHOT_DATE)
.requiredParam(String.class, TinkDaeadFuncConfig.Param.KEY_ID)
.build();
}

private static PseudoFuncConfigPreset redactPseudoFuncConfigPreset(String funcName) {
return PseudoFuncConfigPreset.builder(funcName, RedactFunc.class)
.optionalParam(String.class, RedactFuncConfig.Param.PLACEHOLDER, "*")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ private PseudoFuncNames() {

public static final String FF31 = "ff31";
public static final String DAEAD = "daead";
@Deprecated
public static final String MAP_SID = "map-sid";
public static final String MAP_SID_FF31 = "map-sid-ff31";
public static final String MAP_SID_DAEAD = "map-sid-daead";
public static final String REDACT = "redact";
public static final String FPE = "fpe";

Expand Down
28 changes: 23 additions & 5 deletions src/main/java/no/ssb/dlp/pseudo/core/func/PseudoFuncs.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFunc;
import no.ssb.dapla.dlp.pseudo.func.composite.MapAndEncryptFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFunc;
import no.ssb.dapla.dlp.pseudo.func.fpe.FpeFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.tink.daead.TinkDaeadFunc;
Expand Down Expand Up @@ -56,18 +58,34 @@ static Map<PseudoFuncRule, PseudoFuncConfig> initPseudoFuncConfigs(Collection<Ps

if (FpeFunc.class.getName().equals(funcConfig.getFuncImpl())) {
enrichLegacyFpeFuncConfig(funcConfig, pseudoSecretsMap);
}
else if (TinkDaeadFunc.class.getName().equals(funcConfig.getFuncImpl())) {
} else if (TinkDaeadFunc.class.getName().equals(funcConfig.getFuncImpl())) {
enrichTinkDaeadFuncConfig(funcConfig, pseudoKeysetMap, pseudoSecrets);
}
else if (TinkFpeFunc.class.getName().equals(funcConfig.getFuncImpl())) {
} else if (TinkFpeFunc.class.getName().equals(funcConfig.getFuncImpl())) {
enrichTinkFpeFuncConfig(funcConfig, pseudoKeysetMap, pseudoSecrets);
} else if (MapAndEncryptFunc.class.getName().equals(funcConfig.getFuncImpl())) {
// Repeat the above enrichments for MapAndEncryptFunc
enrichMapAndEncryptFunc(funcConfig, pseudoKeysetMap, pseudoSecretsMap, pseudoSecrets);
}

return funcConfig;
}));
}

private static void enrichMapAndEncryptFunc(PseudoFuncConfig funcConfig,
Map<String, PseudoKeyset> pseudoKeysetMap,
Map<String, PseudoSecret> pseudoSecretsMap,
Collection<PseudoSecret> pseudoSecrets) {
if (FpeFunc.class.getName().equals(funcConfig
.getRequired(MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, String.class))) {
enrichLegacyFpeFuncConfig(funcConfig, pseudoSecretsMap);
} else if (TinkDaeadFunc.class.getName().equals(funcConfig
.getRequired(MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, String.class))) {
enrichTinkDaeadFuncConfig(funcConfig, pseudoKeysetMap, pseudoSecrets);
} else if (TinkFpeFunc.class.getName().equals(funcConfig
.getRequired(MapAndEncryptFuncConfig.Param.ENCRYPTION_FUNC_IMPL, String.class))) {
enrichTinkFpeFuncConfig(funcConfig, pseudoKeysetMap, pseudoSecrets);
}
}

private static void enrichLegacyFpeFuncConfig(PseudoFuncConfig funcConfig, Map<String, PseudoSecret> pseudoSecretsMap) {
String secretId = funcConfig.getRequired(FpeFuncConfig.Param.KEY_ID, String.class);
if (! pseudoSecretsMap.containsKey(secretId)) {
Expand Down
9 changes: 7 additions & 2 deletions src/test/java/no/ssb/dlp/pseudo/core/func/Ff31FuncTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.security.GeneralSecurityException;
import java.util.Map;

import static org.assertj.core.api.Assertions.*;

public class Ff31FuncTest {

@BeforeAll
static void init() throws Exception {
FpeConfig.register();
static void init() {
try {
FpeConfig.register();
} catch (GeneralSecurityException e) {
//Ignore since it may happen in concurrent junit tests
}
}

private final static String KEYSET_JSON_FF31_256_ALPHANUMERIC = "{\"primaryKeyId\":832997605,\"key\":[{\"keyData\":{\"typeUrl\":\"type.googleapis.com/ssb.crypto.tink.FpeFfxKey\",\"value\":\"EiCCNkK81HHmUY4IjEzXDrGLOT5t+7PGQ1eIyrGqGa4S3BpCEAIaPjAxMjM0NTY3ODlBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6\",\"keyMaterialType\":\"SYMMETRIC\"},\"status\":\"ENABLED\",\"keyId\":832997605,\"outputPrefixType\":\"RAW\"}]}";
Expand Down
80 changes: 80 additions & 0 deletions src/test/java/no/ssb/dlp/pseudo/core/func/MapAndDaeadFuncTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package no.ssb.dlp.pseudo.core.func;

import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.DeterministicAead;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.daead.DeterministicAeadConfig;
import no.ssb.crypto.tink.fpe.Fpe;
import no.ssb.crypto.tink.fpe.FpeConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.map.MapFunc;
import no.ssb.dapla.dlp.pseudo.func.map.Mapper;
import no.ssb.dapla.dlp.pseudo.func.tink.daead.TinkDaeadFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.tink.fpe.TinkFpeFuncConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.security.GeneralSecurityException;
import java.util.Map;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

public class MapAndDaeadFuncTest {

@BeforeAll
static void init() {
try {
DeterministicAeadConfig.register();
} catch (GeneralSecurityException e) {
//Ignore since it may happen in concurrent junit tests
}
}

private final static String KEYSET_JSON_AES256_SIV = "{\"primaryKeyId\":1284924461,\"key\":[{\"keyData\":{\"typeUrl\":\"type.googleapis.com/google.crypto.tink.AesSivKey\",\"value\":\"EkCIjYUrKTTMAxEZST8xoyBXrfSLtTt+XmfBcE/PQxhr1Ob+YdD84bSMPQDaTGMqD241C4J7oQ+w3RFXaC8vKzbI\",\"keyMaterialType\":\"SYMMETRIC\"},\"status\":\"ENABLED\",\"keyId\":1284924461,\"outputPrefixType\":\"TINK\"}]}";

private final static Map<String, String> KEYSETS = Map.of(
"1284924461", KEYSET_JSON_AES256_SIV
);

private DeterministicAead daeadPrimitive(String keyId) throws Exception {
if (! KEYSETS.containsKey(keyId)) {
throw new RuntimeException("Unknown keyId: " + keyId);
}

KeysetHandle keysetHandle = CleartextKeysetHandle.read(JsonKeysetReader.withString(KEYSETS.get(keyId)));
return keysetHandle.getPrimitive(DeterministicAead.class);
}

private PseudoFunc f(String funcDecl) throws Exception {
PseudoFuncConfig config = PseudoFuncConfigFactory.get(funcDecl);
String keyId = config.getRequired(TinkFpeFuncConfig.Param.KEY_ID, String.class);
config.add(TinkDaeadFuncConfig.Param.DAEAD, daeadPrimitive(keyId));
return PseudoFuncFactory.create(config);
}

private void transformAndRestore(String originalVal, String expectedVal, PseudoFunc func) {
PseudoFuncOutput pseudonymized = func.apply(PseudoFuncInput.of(originalVal));
assertThat(pseudonymized.getValue()).isEqualTo(expectedVal);
PseudoFuncOutput depseudonymized = func.restore(PseudoFuncInput.of(pseudonymized.getValue()));
assertThat(depseudonymized.getValue()).isEqualTo(originalVal);
}

@Test
void givenText_map_and_daead_shouldEncryptAndDecrypt() throws Exception {
final Mapper mockMapper = mock(Mapper.class);
try (var mapFunc = mockStatic(MapFunc.class)) {
mapFunc.when(() -> MapFunc.loadMapper()).thenReturn(mockMapper);
when(mockMapper.map(eq(PseudoFuncInput.of("Something")))).thenReturn(PseudoFuncOutput.of("Secret"));
when(mockMapper.restore(eq(PseudoFuncInput.of("Secret")))).thenReturn(PseudoFuncOutput.of("Something"));
String funcDeclStr = "map-sid-daead(keyId=1284924461)";
transformAndRestore("Something", "AUyWZC2kWmY72/261fvqshAWQXfy+FY+F7PB", f(funcDeclStr));
}
}

}
77 changes: 77 additions & 0 deletions src/test/java/no/ssb/dlp/pseudo/core/func/MapAndFf31FuncTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package no.ssb.dlp.pseudo.core.func;

import com.google.crypto.tink.CleartextKeysetHandle;
import com.google.crypto.tink.JsonKeysetReader;
import com.google.crypto.tink.KeysetHandle;
import no.ssb.crypto.tink.fpe.Fpe;
import no.ssb.crypto.tink.fpe.FpeConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFunc;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput;
import no.ssb.dapla.dlp.pseudo.func.map.MapFunc;
import no.ssb.dapla.dlp.pseudo.func.map.Mapper;
import no.ssb.dapla.dlp.pseudo.func.tink.fpe.TinkFpeFuncConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.security.GeneralSecurityException;
import java.util.Map;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

public class MapAndFf31FuncTest {

@BeforeAll
static void init() {
try {
FpeConfig.register();
} catch (GeneralSecurityException e) {
//Ignore since it may happen in concurrent junit tests
}
}

private final static String KEYSET_JSON_FF31_256_ALPHANUMERIC = "{\"primaryKeyId\":832997605,\"key\":[{\"keyData\":{\"typeUrl\":\"type.googleapis.com/ssb.crypto.tink.FpeFfxKey\",\"value\":\"EiCCNkK81HHmUY4IjEzXDrGLOT5t+7PGQ1eIyrGqGa4S3BpCEAIaPjAxMjM0NTY3ODlBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6\",\"keyMaterialType\":\"SYMMETRIC\"},\"status\":\"ENABLED\",\"keyId\":832997605,\"outputPrefixType\":\"RAW\"}]}";

private final static Map<String, String> KEYSETS = Map.of(
"1234567890", KEYSET_JSON_FF31_256_ALPHANUMERIC
);

private Fpe fpePrimitive(String keyId) throws Exception {
if (! KEYSETS.containsKey(keyId)) {
throw new RuntimeException("Unknown keyId: " + keyId);
}

KeysetHandle keysetHandle = CleartextKeysetHandle.read(JsonKeysetReader.withString(KEYSETS.get(keyId)));
return keysetHandle.getPrimitive(Fpe.class);
}

private PseudoFunc f(String funcDecl) throws Exception {
PseudoFuncConfig config = PseudoFuncConfigFactory.get(funcDecl);
String keyId = config.getRequired(TinkFpeFuncConfig.Param.KEY_ID, String.class);
config.add(TinkFpeFuncConfig.Param.FPE, fpePrimitive(keyId));
return PseudoFuncFactory.create(config);
}

private void transformAndRestore(String originalVal, String expectedVal, PseudoFunc func) {
PseudoFuncOutput pseudonymized = func.apply(PseudoFuncInput.of(originalVal));
assertThat(pseudonymized.getValue()).isEqualTo(expectedVal);
PseudoFuncOutput depseudonymized = func.restore(PseudoFuncInput.of(pseudonymized.getValue()));
assertThat(depseudonymized.getValue()).isEqualTo(originalVal);
}

@Test
void givenText_map_and_ff31_shouldEncryptAndDecrypt() throws Exception {
final Mapper mockMapper = mock(Mapper.class);
try (var mapFunc = mockStatic(MapFunc.class)) {
mapFunc.when(() -> MapFunc.loadMapper()).thenReturn(mockMapper);
when(mockMapper.map(eq(PseudoFuncInput.of("Something")))).thenReturn(PseudoFuncOutput.of("Secret"));
when(mockMapper.restore(eq(PseudoFuncInput.of("Secret")))).thenReturn(PseudoFuncOutput.of("Something"));
String funcDeclStr = "map-sid-ff31(keyId=1234567890)";
transformAndRestore("Something", "CQqlS3", f(funcDeclStr));
}
}

}
3 changes: 2 additions & 1 deletion src/test/java/no/ssb/dlp/pseudo/core/func/MapFuncTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncFactory;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput;
import no.ssb.dapla.dlp.pseudo.func.TransformDirection;
import no.ssb.dapla.dlp.pseudo.func.map.MapFunc;
import no.ssb.dapla.dlp.pseudo.func.map.Mapper;
import org.junit.jupiter.api.Test;
Expand All @@ -26,7 +27,7 @@ void mapFuncWithTimestamp() {
mapFunc.when(() -> MapFunc.loadMapper()).thenReturn(mockMapper);
String funcDeclStr = "map-sid(keyId=1284924461, snapshotDate=2023-05-21)";
PseudoFunc func = f(funcDeclStr);
func.init(PseudoFuncInput.of("50607080901"));
func.init(PseudoFuncInput.of("50607080901"), TransformDirection.APPLY);
}
// Check that the mockMapper has received the versionTimestamp
ArgumentCaptor<Map> argumentsCaptured = ArgumentCaptor.forClass(Map.class);
Expand Down
Loading