Skip to content

Commit

Permalink
Compose new map-and-encrypt functions (#39)
Browse files Browse the repository at this point in the history
* Use the new MapAndEncryptFunc to compose the functions map-sid-ff31 and map-sid-daead.
  • Loading branch information
bjornandre authored Mar 14, 2024
1 parent b518749 commit ab0efa4
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 9 deletions.
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

0 comments on commit ab0efa4

Please sign in to comment.