From c9c44880c33603aa86e12e4fc7dd35a30d5a1812 Mon Sep 17 00:00:00 2001 From: Kenneth Leine Schulstad Date: Mon, 13 Mar 2023 13:37:42 +0100 Subject: [PATCH] Add tink fpe (#5) Add support for the Tink FPE library. Refactored some of the code related the existing Tink features (tink-daead). Should be read together with the changes in dapla-dlp-pseudo-core. * Add tink FPE function + refactor daead * Test cases for FAIL and SKIP strategies * Refactor * Update dependencies --------- Co-authored-by: Miles Mason Winther --- pom.xml | 23 ++-- .../dlp/pseudo/func/PseudoFuncConfig.java | 5 +- .../func/daead/DaeadFuncConfigService.java | 19 ---- .../dlp/pseudo/func/fpe/FpeConfigService.java | 2 +- .../dlp/pseudo/func/fpe/FpeFuncConfig.java | 3 +- .../dapla/dlp/pseudo/func/map/MapFunc.java | 2 - .../dlp/pseudo/func/redact/RedactFunc.java | 11 +- .../daead/TinkDaeadFunc.java} | 11 +- .../daead/TinkDaeadFuncConfig.java} | 7 +- .../daead/TinkDaeadFuncConfigService.java | 19 ++++ .../dlp/pseudo/func/tink/fpe/TinkFpeFunc.java | 66 ++++++++++++ .../func/tink/fpe/TinkFpeFuncConfig.java | 23 ++++ .../tink/fpe/TinkFpeFuncConfigService.java | 31 ++++++ .../dlp/pseudo/func/util/FromString.java | 21 +++- .../pseudo/func/PseudoFuncFactoryTest.java | 14 +-- .../dlp/pseudo/func/fpe/FpeFuncTest.java | 12 +-- .../func/{ => tink}/daead/DaeadWrapper.java | 2 +- .../daead/TinkDaeadFuncTest.java} | 12 +-- .../dlp/pseudo/func/tink/fpe/FpeWrapper.java | 62 +++++++++++ .../pseudo/func/tink/fpe/TinkFpeFuncTest.java | 102 ++++++++++++++++++ .../dlp/pseudo/func/util/FromStringTest.java | 28 +++++ 21 files changed, 409 insertions(+), 66 deletions(-) delete mode 100644 src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfigService.java rename src/main/java/no/ssb/dapla/dlp/pseudo/func/{daead/DaeadFunc.java => tink/daead/TinkDaeadFunc.java} (87%) rename src/main/java/no/ssb/dapla/dlp/pseudo/func/{daead/DaeadFuncConfig.java => tink/daead/TinkDaeadFuncConfig.java} (66%) create mode 100644 src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfigService.java create mode 100644 src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFunc.java create mode 100644 src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfig.java create mode 100644 src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfigService.java rename src/test/java/no/ssb/dapla/dlp/pseudo/func/{ => tink}/daead/DaeadWrapper.java (97%) rename src/test/java/no/ssb/dapla/dlp/pseudo/func/{daead/DaeadFuncTest.java => tink/daead/TinkDaeadFuncTest.java} (86%) create mode 100644 src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/FpeWrapper.java create mode 100644 src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncTest.java diff --git a/pom.xml b/pom.xml index dc19758..163f079 100644 --- a/pom.xml +++ b/pom.xml @@ -16,27 +16,28 @@ artifactregistry://europe-north1-maven.pkg.dev/artifact-registry-14da - 3.23.1 + 3.24.2 1.0.0 31.1-jre - 2.14.0 + 2.14.1 1.5.1 - 5.9.1 + 5.9.2 1.4.5 - 1.18.24 - 1.7.36 - 1.7.0 + 1.18.26 + 2.0.6 + 1.8.0 + 0.0.1 2.1.4 - 9.3 + 10.8.1 3.1.2 3.8.1 3.3.0 3.2.4 2.22.2 3.2.1 - 0.8.7 + 0.8.8 3.9.1.2184 @@ -58,7 +59,11 @@ tink ${tink.version} - + + no.ssb.crypto.tink + tink-fpe-java + ${tink-fpe-java.version} + org.projectlombok lombok diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncConfig.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncConfig.java index 6d251eb..2003019 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncConfig.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncConfig.java @@ -19,7 +19,6 @@ public class PseudoFuncConfig implements Serializable { @Getter private final String funcImpl; - private final Map config = new HashMap<>(); /** Construct from Map */ @@ -48,6 +47,10 @@ public T getRequired(String paramName, Class clazz) { .orElseThrow(() -> new PseudoFuncMissingParamException(paramName)); } + public Map asMap() { + return Map.copyOf(config); + } + public void add(String paramName, Object o) { config.put(paramName, o); } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfigService.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfigService.java deleted file mode 100644 index 12c6809..0000000 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfigService.java +++ /dev/null @@ -1,19 +0,0 @@ -package no.ssb.dapla.dlp.pseudo.func.daead; - -import com.google.crypto.tink.DeterministicAead; -import lombok.extern.slf4j.Slf4j; -import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig; - -import static no.ssb.dapla.dlp.pseudo.func.daead.DaeadFuncConfig.Param.DAEAD; - -@Slf4j -public class DaeadFuncConfigService { - - public DaeadFuncConfig resolve(PseudoFuncConfig cfg) { - - return DaeadFuncConfig.builder() - .daead(cfg.getRequired(DAEAD, DeterministicAead.class)) - .build(); - } - -} diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeConfigService.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeConfigService.java index 1b35257..32a513c 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeConfigService.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeConfigService.java @@ -10,7 +10,7 @@ public class FpeConfigService { public FpeFuncConfig resolve(PseudoFuncConfig genericConfig) { String alphabet = genericConfig.getRequired(FpeFuncConfig.Param.ALPHABET, String.class); - String base64EncodedKey = genericConfig.getRequired(FpeFuncConfig.Param.KEY, String.class); + String base64EncodedKey = genericConfig.getRequired(FpeFuncConfig.Param.KEY_DATA, String.class); Boolean isReplaceIllegalChars = genericConfig.get(FpeFuncConfig.Param.REPLACE_ILLEGAL_CHARS, Boolean.class).orElse(true); String illegalCharReplacement = genericConfig.get(FpeFuncConfig.Param.REPLACE_ILLEGAL_CHARS_WITH, String.class).orElse(null); diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncConfig.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncConfig.java index ffb2d1c..85c96ef 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncConfig.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncConfig.java @@ -16,8 +16,9 @@ public class FpeFuncConfig { @UtilityClass public static class Param { public static final String ALPHABET = "alphabet"; + public static final String KEY_ID = "keyId"; - public static final String KEY = "key"; + public static final String KEY_DATA = "keyData"; public static final String REPLACE_ILLEGAL_CHARS = "replaceIllegalChars"; public static final String REPLACE_ILLEGAL_CHARS_WITH = "replaceIllegalCharsWith"; } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java index 49a722a..ad8bf46 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java @@ -30,7 +30,6 @@ public PseudoFuncOutput apply(PseudoFuncInput input) { for (Object inputValue : input.getValues()) { String plain = String.valueOf(inputValue); final Object pseudonymized = mapper.map(plain); - //output.add(FromString.convert(pseudonymized, inputValue.getClass())); output.add(pseudonymized); } @@ -44,7 +43,6 @@ public PseudoFuncOutput restore(PseudoFuncInput input) { for (Object inputValue : input.getValues()) { String mapped = String.valueOf(inputValue); final Object clear = mapper.restore(mapped); - //output.add(FromString.convert(clear, inputValue.getClass())); output.add(clear); } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/redact/RedactFunc.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/redact/RedactFunc.java index 1aa58e6..02450f6 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/redact/RedactFunc.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/redact/RedactFunc.java @@ -1,5 +1,6 @@ package no.ssb.dapla.dlp.pseudo.func.redact; +import com.google.common.base.Strings; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import no.ssb.dapla.dlp.pseudo.func.AbstractPseudoFunc; @@ -7,6 +8,9 @@ import no.ssb.dapla.dlp.pseudo.func.PseudoFuncInput; import no.ssb.dapla.dlp.pseudo.func.PseudoFuncOutput; +import static com.google.common.base.Strings.emptyToNull; +import static com.google.common.base.Strings.nullToEmpty; + @Slf4j public class RedactFunc extends AbstractPseudoFunc { private final RedactFuncConfigService configService = new RedactFuncConfigService(); @@ -22,7 +26,10 @@ public PseudoFuncOutput apply(PseudoFuncInput input) { PseudoFuncOutput output = new PseudoFuncOutput(); input.getValues().forEach(in -> { String plain = String.valueOf(in); - if (config.getRegex() != null) { + if (plain.trim().isEmpty()) { + output.add(plain); + } + else if (config.getRegex() != null) { output.add(plain.replaceAll(config.getRegex(), config.getPlaceholder())); } else { @@ -36,7 +43,7 @@ public PseudoFuncOutput apply(PseudoFuncInput input) { @Override public PseudoFuncOutput restore(PseudoFuncInput input) { PseudoFuncOutput output = new PseudoFuncOutput(); - input.getValues().forEach(in -> output.add(in)); + input.getValues().forEach(output::add); return output; } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFunc.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFunc.java similarity index 87% rename from src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFunc.java rename to src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFunc.java index d472ccb..4158a22 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFunc.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFunc.java @@ -1,4 +1,4 @@ -package no.ssb.dapla.dlp.pseudo.func.daead; +package no.ssb.dapla.dlp.pseudo.func.tink.daead; import com.google.crypto.tink.DeterministicAead; import lombok.NonNull; @@ -11,14 +11,13 @@ import java.util.Base64; @Slf4j -public class DaeadFunc extends AbstractPseudoFunc { +public class TinkDaeadFunc extends AbstractPseudoFunc { - // FIXME: Replace this with something real private static final byte[] DAEAD_STAMP_BYTES = "".getBytes(StandardCharsets.UTF_8); - private final DaeadFuncConfigService configService = new DaeadFuncConfigService(); - private final DaeadFuncConfig config; + private final TinkDaeadFuncConfigService configService = new TinkDaeadFuncConfigService(); + private final TinkDaeadFuncConfig config; - public DaeadFunc(@NonNull PseudoFuncConfig genericConfig) { + public TinkDaeadFunc(@NonNull PseudoFuncConfig genericConfig) { super(genericConfig.getFuncDecl()); this.config = configService.resolve(genericConfig); } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfig.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfig.java similarity index 66% rename from src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfig.java rename to src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfig.java index ab2216f..47826e4 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncConfig.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfig.java @@ -1,4 +1,4 @@ -package no.ssb.dapla.dlp.pseudo.func.daead; +package no.ssb.dapla.dlp.pseudo.func.tink.daead; import com.google.crypto.tink.DeterministicAead; import lombok.Builder; @@ -7,15 +7,14 @@ @Value @Builder -public class DaeadFuncConfig { +public class TinkDaeadFuncConfig { private final String dataEncryptionKeyId; private final String base64EncodedWrappedDataEncryptionKey; private final DeterministicAead daead; @UtilityClass public static class Param { - public static final String DEK_ID = "dataEncryptionKeyId"; - public static final String WDEK = "wrappedDataEncryptionKey"; public static final String DAEAD = "deterministicAead"; + public static final String KEY_ID = "keyId"; } } diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfigService.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfigService.java new file mode 100644 index 0000000..21a8cb5 --- /dev/null +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncConfigService.java @@ -0,0 +1,19 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.daead; + +import com.google.crypto.tink.DeterministicAead; +import lombok.extern.slf4j.Slf4j; +import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig; + +import static no.ssb.dapla.dlp.pseudo.func.tink.daead.TinkDaeadFuncConfig.Param.DAEAD; + +@Slf4j +public class TinkDaeadFuncConfigService { + + public TinkDaeadFuncConfig resolve(PseudoFuncConfig cfg) { + + return TinkDaeadFuncConfig.builder() + .daead(cfg.getRequired(DAEAD, DeterministicAead.class)) + .build(); + } + +} diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFunc.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFunc.java new file mode 100644 index 0000000..ae9b983 --- /dev/null +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFunc.java @@ -0,0 +1,66 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.fpe; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import no.ssb.crypto.tink.fpe.Fpe; +import no.ssb.dapla.dlp.pseudo.func.*; + +import java.security.GeneralSecurityException; + +import static no.ssb.crypto.tink.fpe.util.ByteArrayUtil.b2s; +import static no.ssb.crypto.tink.fpe.util.ByteArrayUtil.s2b; + +@Slf4j +public class TinkFpeFunc extends AbstractPseudoFunc { + private final TinkFpeFuncConfigService configService = new TinkFpeFuncConfigService(); + private final TinkFpeFuncConfig config; + + public TinkFpeFunc(@NonNull PseudoFuncConfig genericConfig) { + super(genericConfig.getFuncDecl()); + this.config = configService.resolve(genericConfig); + } + + private Fpe fpe() { + return config.getFpe(); + } + + @Override + public PseudoFuncOutput apply(PseudoFuncInput input) { + PseudoFuncOutput output = new PseudoFuncOutput(); + input.getValues().forEach(in -> { + String plain = String.valueOf(in); + try { + byte[] ciphertext = fpe().encrypt(s2b(plain), config.getFpeParams()); + output.add(b2s(ciphertext)); + } + catch (GeneralSecurityException e) { + throw new TinkFpePseudoFuncException("Tink FPE apply error. func=" + getFuncDecl() + ", contentType=" + input.getParamMetadata(), e); + } + }); + + return output; + } + + @Override + public PseudoFuncOutput restore(PseudoFuncInput input) { + PseudoFuncOutput output = new PseudoFuncOutput(); + input.getValues().forEach(in -> { + byte[] ciphertext = s2b(String.valueOf(in)); + try { + byte[] plaintext = fpe().decrypt(ciphertext, config.getFpeParams()); + output.add(b2s(plaintext)); + } + catch (GeneralSecurityException e) { + throw new TinkFpePseudoFuncException("Tink FPE restore error. func=" + getFuncDecl() + ", contentType=" + input.getParamMetadata(), e); + } + }); + + return output; + } + + public static class TinkFpePseudoFuncException extends PseudoFuncException { + public TinkFpePseudoFuncException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfig.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfig.java new file mode 100644 index 0000000..7e2ab5a --- /dev/null +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfig.java @@ -0,0 +1,23 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.fpe; + +import lombok.Builder; +import lombok.Value; +import lombok.experimental.UtilityClass; +import no.ssb.crypto.tink.fpe.Fpe; +import no.ssb.crypto.tink.fpe.FpeParams; + +@Value +@Builder +public class TinkFpeFuncConfig { + private final Fpe fpe; + private final FpeParams fpeParams; + + @UtilityClass + public static class Param { + public static final String FPE = "fpe"; + public static final String KEY_ID = "keyId"; + public static final String UNKNOWN_CHARACTER_STRATEGY = "strategy"; + public static final String REDACT_CHAR = "redactChar"; + public static final String TWEAK = "tweak"; + } +} diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfigService.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfigService.java new file mode 100644 index 0000000..dee6db0 --- /dev/null +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncConfigService.java @@ -0,0 +1,31 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.fpe; + +import lombok.extern.slf4j.Slf4j; +import no.ssb.crypto.tink.fpe.Fpe; +import no.ssb.crypto.tink.fpe.FpeParams; +import no.ssb.crypto.tink.fpe.UnknownCharacterStrategy; +import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig; + +import static no.ssb.dapla.dlp.pseudo.func.tink.fpe.TinkFpeFuncConfig.Param.*; + +@Slf4j +public class TinkFpeFuncConfigService { + + public TinkFpeFuncConfig resolve(PseudoFuncConfig cfg) { + FpeParams fpeParams = FpeParams.DEFAULT; + if (cfg.has(UNKNOWN_CHARACTER_STRATEGY)) { + fpeParams.unknownCharacterStrategy(cfg.getRequired(UNKNOWN_CHARACTER_STRATEGY, UnknownCharacterStrategy.class)); + } + if (cfg.has(REDACT_CHAR)) { + fpeParams.redactionChar(cfg.getRequired(REDACT_CHAR, Character.class)); + } + + //UnknownCharacterStrategy strategy = cfg.get(UNKNOWN_CHARACTER_STRATEGY, UnknownCharacterStrategy.class).orElse(UnknownCharacterStrategy.FAIL); + + return TinkFpeFuncConfig.builder() + .fpe(cfg.getRequired(FPE, Fpe.class)) + .fpeParams(fpeParams) + .build(); + } + +} diff --git a/src/main/java/no/ssb/dapla/dlp/pseudo/func/util/FromString.java b/src/main/java/no/ssb/dapla/dlp/pseudo/func/util/FromString.java index 1e38c2f..3f9b641 100644 --- a/src/main/java/no/ssb/dapla/dlp/pseudo/func/util/FromString.java +++ b/src/main/java/no/ssb/dapla/dlp/pseudo/func/util/FromString.java @@ -16,6 +16,7 @@ public class FromString { private static Map, Function> fromStringFunctionMap = ImmutableMap., Function>builder() .put(String.class, s -> s) + .put(Character.class, s -> (blankToNull(s) == null) ? null : Character.valueOf(s.charAt(0))) .put(Long.class, s -> (blankToNull(s) == null) ? null : Long.valueOf(s)) .put(Integer.class, s -> (blankToNull(s) == null) ? null : Integer.valueOf(s)) .put(Double.class, s -> (blankToNull(s) == null) ? null : Double.valueOf(s)) @@ -36,8 +37,26 @@ private static String blankToNull(String s) { return emptyToNull(nullToEmpty(s).trim()); } + private static > E convertEnum(String name, Class clazz) { + if (blankToNull(name) == null) { + return null; + } + for (E e : clazz.getEnumConstants()) { + if (name.equalsIgnoreCase(e.toString())) { + return e; + } + } + + throw new IllegalArgumentException("No matching enum value '" + name + "' in enum " + clazz); + } + public static T convert(String data, Class clazz) { - return (T) fromStringFunctionMap.get(clazz).apply(data); + if (clazz.isEnum()) { + return (T) convertEnum(data, (Class) clazz); + } + else { + return (T) fromStringFunctionMap.get(clazz).apply(data); + } } } diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncFactoryTest.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncFactoryTest.java index f0ce670..55f979b 100644 --- a/src/test/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncFactoryTest.java +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/PseudoFuncFactoryTest.java @@ -18,7 +18,7 @@ class PseudoFuncFactoryTest { private final static String KEY_ID = "01DWENC90WW9K41EN0QS2Q23X4"; - private final static String KEY_CONTENT = "8weo9VlQTuPqxjVWaHAupOdCwNpn4CFz"; + private final static String KEY_MATERIAL = "8weo9VlQTuPqxjVWaHAupOdCwNpn4CFz"; private final static List GENERIC_CONFIG = ImmutableList.of( new PseudoFuncConfig(ImmutableMap.of( @@ -26,7 +26,7 @@ class PseudoFuncFactoryTest { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "digits", FpeFuncConfig.Param.KEY_ID, KEY_ID, - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL )), new PseudoFuncConfig(ImmutableMap.of( @@ -34,7 +34,7 @@ class PseudoFuncFactoryTest { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "alphanumeric+whitespace", FpeFuncConfig.Param.KEY_ID, "01DWENC90WW9K41EN0QS2Q23X4", - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL )), new PseudoFuncConfig(ImmutableMap.of( @@ -42,7 +42,7 @@ class PseudoFuncFactoryTest { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "alphanumeric+whitespace", FpeFuncConfig.Param.KEY_ID, KEY_ID, - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL )) ); @@ -60,7 +60,7 @@ void missingDeclParam_create_shouldFailWithProperError() { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, CharacterGroup.ALPHANUMERIC.getChars(), FpeFuncConfig.Param.KEY_ID, KEY_ID, - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL ); PseudoFuncException e = assertThrows(PseudoFuncException.class, () -> { PseudoFuncConfig config = new PseudoFuncConfig(params); @@ -76,7 +76,7 @@ void missingImplParam_create_shouldFailWithProperError() { PseudoFuncConfig.Param.FUNC_DECL, "fpe-alphanumeric(param1)", FpeFuncConfig.Param.ALPHABET, CharacterGroup.ALPHANUMERIC.getChars(), FpeFuncConfig.Param.KEY_ID, KEY_ID, - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL ); PseudoFuncException e = assertThrows(PseudoFuncException.class, () -> { PseudoFuncConfig config = new PseudoFuncConfig(params); @@ -92,7 +92,7 @@ void missingAlphabetParam_create_shouldFailWithProperError() { PseudoFuncConfig.Param.FUNC_DECL, "fpe-text", PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.KEY_ID, KEY_ID, - FpeFuncConfig.Param.KEY, KEY_CONTENT + FpeFuncConfig.Param.KEY_DATA, KEY_MATERIAL ); PseudoFuncException e = assertThrows(PseudoFuncException.class, () -> { PseudoFuncConfig config = new PseudoFuncConfig(params); diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncTest.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncTest.java index 0a1990f..8ddea32 100644 --- a/src/test/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncTest.java +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/fpe/FpeFuncTest.java @@ -24,7 +24,7 @@ void alphanumeric_fpe_shouldTransformAndRestore() { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "alphanumeric+whitespace", FpeFuncConfig.Param.KEY_ID, "keyId1", - FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY + FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY ))); } @@ -38,7 +38,7 @@ void multipleAlphanumeric_fpe_shouldTransformAndRestore() { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "alphanumeric+whitespace+symbols", FpeFuncConfig.Param.KEY_ID, "keyId1", - FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY + FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY ))); } @@ -54,7 +54,7 @@ void digits_fpe_shouldTransformAndRestore(String originalVal, String expectedVal PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "digits", FpeFuncConfig.Param.KEY_ID, "keyId1", - FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY + FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY ))); } @@ -67,7 +67,7 @@ void customAlphabet_fpe_shouldTransformAndRestore() { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "ABCDEFGHIJ", FpeFuncConfig.Param.KEY_ID, "keyId1", - FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY + FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY ))); } @@ -80,7 +80,7 @@ void stringWithIllegalChars_fpe_shouldNotFailPseudonumization() { .put(PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName()) .put(FpeFuncConfig.Param.ALPHABET, "ABCDEFGHIJ") .put(FpeFuncConfig.Param.KEY_ID, "keyId1") - .put(FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY) + .put(FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY) .build() )); @@ -98,7 +98,7 @@ void longValue_fpe_shouldTransformAndRestore() { PseudoFuncConfig.Param.FUNC_IMPL, FpeFunc.class.getName(), FpeFuncConfig.Param.ALPHABET, "digits", FpeFuncConfig.Param.KEY_ID, "keyId1", - FpeFuncConfig.Param.KEY, BASE64_ENCODED_KEY + FpeFuncConfig.Param.KEY_DATA, BASE64_ENCODED_KEY ))); } diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadWrapper.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/DaeadWrapper.java similarity index 97% rename from src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadWrapper.java rename to src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/DaeadWrapper.java index 40b0b44..a3d53e8 100644 --- a/src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadWrapper.java +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/DaeadWrapper.java @@ -1,4 +1,4 @@ -package no.ssb.dapla.dlp.pseudo.func.daead; +package no.ssb.dapla.dlp.pseudo.func.tink.daead; import com.google.crypto.tink.*; diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncTest.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncTest.java similarity index 86% rename from src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncTest.java rename to src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncTest.java index f22fe40..a259bbf 100644 --- a/src/test/java/no/ssb/dapla/dlp/pseudo/func/daead/DaeadFuncTest.java +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/daead/TinkDaeadFuncTest.java @@ -1,4 +1,4 @@ -package no.ssb.dapla.dlp.pseudo.func.daead; +package no.ssb.dapla.dlp.pseudo.func.tink.daead; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; -class DaeadFuncTest { +class TinkDaeadFuncTest { @BeforeAll static void initTink() throws GeneralSecurityException { @@ -25,8 +25,8 @@ void alphanumeric_daead_shouldTransformAndRestore() { String originalVal = "Ken sent me"; transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( PseudoFuncConfig.Param.FUNC_DECL, String.format("tink-daead(%s)", daeadWrapper.getKeyId()), - PseudoFuncConfig.Param.FUNC_IMPL, DaeadFunc.class.getName(), - DaeadFuncConfig.Param.DAEAD, daeadWrapper.getDaead() + PseudoFuncConfig.Param.FUNC_IMPL, TinkDaeadFunc.class.getName(), + TinkDaeadFuncConfig.Param.DAEAD, daeadWrapper.getDaead() ))); } @@ -36,8 +36,8 @@ void multipleAlphanumeric_daead_shouldTransformAndRestore() { List originalVal = ImmutableList.of("Ken sent me...", "Kilroy was here!"); transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( PseudoFuncConfig.Param.FUNC_DECL, String.format("tink-daead(%s)", daeadWrapper.getKeyId()), - PseudoFuncConfig.Param.FUNC_IMPL, DaeadFunc.class.getName(), - DaeadFuncConfig.Param.DAEAD, daeadWrapper.getDaead() + PseudoFuncConfig.Param.FUNC_IMPL, TinkDaeadFunc.class.getName(), + TinkDaeadFuncConfig.Param.DAEAD, daeadWrapper.getDaead() ))); } diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/FpeWrapper.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/FpeWrapper.java new file mode 100644 index 0000000..4dfed9a --- /dev/null +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/FpeWrapper.java @@ -0,0 +1,62 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.fpe; + +import com.google.crypto.tink.CleartextKeysetHandle; +import com.google.crypto.tink.JsonKeysetWriter; +import com.google.crypto.tink.KeyTemplates; +import com.google.crypto.tink.KeysetHandle; +import no.ssb.crypto.tink.fpe.Fpe; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; + +public class FpeWrapper { + + private final KeysetHandle keysetHandle; + + private final Fpe fpe; + + public FpeWrapper() { + try { + this.keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("FPE_FF31_256_ALPHANUMERIC")); + this.fpe = keysetHandle.getPrimitive(Fpe.class); + } + catch (GeneralSecurityException e) { + throw new FpeWrapperException("Error initializing FpeWrapper for testing", e); + } + } + + private static String toKeyJson(KeysetHandle keysetHandle) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withOutputStream(baos)); + return new String(baos.toByteArray()); + } + + public String getKeyJson() { + try { + return toKeyJson(keysetHandle); + } + catch (IOException e) { + throw new FpeWrapperException("Error deducing keyJson", e); + } + } + + public String getKeyId() { + try { + return String.valueOf(keysetHandle.primaryKey().getId()); + } + catch (GeneralSecurityException e) { + throw new FpeWrapperException("Error deducing keyId", e); + } + } + + public Fpe getFpe() { + return fpe; + } + + public static class FpeWrapperException extends RuntimeException { + public FpeWrapperException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncTest.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncTest.java new file mode 100644 index 0000000..f38cc93 --- /dev/null +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/tink/fpe/TinkFpeFuncTest.java @@ -0,0 +1,102 @@ +package no.ssb.dapla.dlp.pseudo.func.tink.fpe; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import no.ssb.crypto.tink.fpe.FpeConfig; +import no.ssb.crypto.tink.fpe.IncompatiblePlaintextException; +import no.ssb.crypto.tink.fpe.UnknownCharacterStrategy; +import no.ssb.dapla.dlp.pseudo.func.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.security.GeneralSecurityException; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +class TinkFpeFuncTest { + + @BeforeAll + static void initTink() throws GeneralSecurityException { + FpeConfig.register(); + } + + @Test + void alphanumeric_fpe_fail_onlyAcceptedCharacters() { + FpeWrapper fpeWrapper = new FpeWrapper(); + String originalVal = "StringContainingOnlyAcceptedAlphabetCharacters"; + transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( + PseudoFuncConfig.Param.FUNC_DECL, String.format("ff31(%s)", fpeWrapper.getKeyId()), + PseudoFuncConfig.Param.FUNC_IMPL, TinkFpeFunc.class.getName(), + TinkFpeFuncConfig.Param.FPE, fpeWrapper.getFpe() + ))); + } + + @Test + void alphanumeric_fpe_fail_charactersExcludedFromAlphabet() { + FpeWrapper fpeWrapper = new FpeWrapper(); + String originalVal = "String with spaces"; + assertThatExceptionOfType(IncompatiblePlaintextException.class).isThrownBy(() -> + transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( + PseudoFuncConfig.Param.FUNC_DECL, String.format("ff31(%s)", fpeWrapper.getKeyId()), + PseudoFuncConfig.Param.FUNC_IMPL, TinkFpeFunc.class.getName(), + TinkFpeFuncConfig.Param.FPE, fpeWrapper.getFpe() + ))) + ); + } + + @Test + @Disabled + void alphanumeric_fpe_fail_shortString() { + // TODO Test fails: Should this case not throw an exception?? + FpeWrapper fpeWrapper = new FpeWrapper(); + String originalVal = "Hi"; + assertThatExceptionOfType(IncompatiblePlaintextException.class).isThrownBy(() -> + transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( + PseudoFuncConfig.Param.FUNC_DECL, String.format("ff31(%s)", fpeWrapper.getKeyId()), + PseudoFuncConfig.Param.FUNC_IMPL, TinkFpeFunc.class.getName(), + TinkFpeFuncConfig.Param.FPE, fpeWrapper.getFpe() + ))) + ); + } + + @Test + void alphanumeric_fpe_skip_shouldTransformAndRestore() { + FpeWrapper fpeWrapper = new FpeWrapper(); + String originalVal = "Åge Åsnes"; + transformAndRestore(originalVal, new PseudoFuncConfig(ImmutableMap.of( + PseudoFuncConfig.Param.FUNC_DECL, String.format("ff31(%s)", fpeWrapper.getKeyId()), + PseudoFuncConfig.Param.FUNC_IMPL, TinkFpeFunc.class.getName(), + TinkFpeFuncConfig.Param.FPE, fpeWrapper.getFpe(), + TinkFpeFuncConfig.Param.UNKNOWN_CHARACTER_STRATEGY, UnknownCharacterStrategy.SKIP + //FpeFuncConfig.Param.FPE_PARAMS, FpeParams.with().unknownCharacterStrategy(UnknownCharacterStrategy.SKIP) + ))); + } + + @Test + void alphanumeric_fpe_skip_charactersOutsideAlphabetSkipped() { + FpeWrapper fpeWrapper = new FpeWrapper(); + String originalVal = "Åge Åsnes"; + PseudoFunc func = PseudoFuncFactory.create(new PseudoFuncConfig(ImmutableMap.of( + PseudoFuncConfig.Param.FUNC_DECL, String.format("tink-fpe(%s)", fpeWrapper.getKeyId()), + PseudoFuncConfig.Param.FUNC_IMPL, TinkFpeFunc.class.getName(), + TinkFpeFuncConfig.Param.FPE, fpeWrapper.getFpe(), + TinkFpeFuncConfig.Param.UNKNOWN_CHARACTER_STRATEGY, UnknownCharacterStrategy.SKIP + ))); + PseudoFuncOutput pseudonymized = func.apply(PseudoFuncInput.of(originalVal)); + String pseudonymizedText = pseudonymized.getStringValues().iterator().next(); + System.out.println(pseudonymizedText); + assertThat(pseudonymizedText).matches(Pattern.compile("^Å.{2}\\sÅ.{4}$")); + } + + private void transformAndRestore(Object originalVal, PseudoFuncConfig config) { + Iterable originalElements = (originalVal instanceof Iterable) ? (Iterable) originalVal : ImmutableList.of(originalVal); + PseudoFunc func = PseudoFuncFactory.create(config); + PseudoFuncOutput pseudonymized = func.apply(PseudoFuncInput.of(originalVal)); + System.out.println(pseudonymized.getValues()); + PseudoFuncOutput depseudonymized = func.restore(PseudoFuncInput.of(pseudonymized.getValues())); + assertThat(depseudonymized.getValues()).containsExactlyElementsOf(originalElements); + } +} \ No newline at end of file diff --git a/src/test/java/no/ssb/dapla/dlp/pseudo/func/util/FromStringTest.java b/src/test/java/no/ssb/dapla/dlp/pseudo/func/util/FromStringTest.java index 7e47982..af34d20 100644 --- a/src/test/java/no/ssb/dapla/dlp/pseudo/func/util/FromStringTest.java +++ b/src/test/java/no/ssb/dapla/dlp/pseudo/func/util/FromStringTest.java @@ -6,6 +6,7 @@ import java.math.BigInteger; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class FromStringTest { @@ -65,6 +66,30 @@ void stringValue_shouldConvertToString() { assertThat(FromString.convert(null, String.class)).isNull(); } + @Test + void stringValue_shouldConvertToCharacter() { + assertThat(FromString.convert("", Character.class)).isNull(); + assertThat(FromString.convert(" ", Character.class)).isNull(); + assertThat(FromString.convert("abc", Character.class)).isEqualTo('a'); + assertThat(FromString.convert(null, Character.class)).isNull(); + } + + @Test + void stringValue_shouldConvertToEnum() { + assertThat(FromString.convert("VAL1", SomeEnum.class)).isEqualTo(SomeEnum.VAL1); + assertThat(FromString.convert("val1", SomeEnum.class)).isEqualTo(SomeEnum.VAL1); + assertThat(FromString.convert("vAl2", SomeEnum.class)).isEqualTo(SomeEnum.VAL2); + assertThat(FromString.convert("", SomeEnum.class)).isNull(); + assertThat(FromString.convert(" ", SomeEnum.class)).isNull(); + assertThat(FromString.convert(null, SomeEnum.class)).isNull(); + + assertThatThrownBy(() -> { + FromString.convert("val3", SomeEnum.class); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("No matching enum value 'val3' in enum class"); + } + @Test void stringValue_shouldConvertToBoolean() { assertThat(FromString.convert("true", Boolean.class)).isEqualTo(Boolean.TRUE); @@ -79,4 +104,7 @@ void stringValue_shouldConvertToBoolean() { assertThat(FromString.convert(null, Boolean.class)).isNull(); } + private enum SomeEnum { + VAL1, VAL2; + } } \ No newline at end of file