Skip to content

Commit

Permalink
Add tink fpe (#5)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
kschulst and mmwinther authored Mar 13, 2023
1 parent caefb40 commit c9c4488
Show file tree
Hide file tree
Showing 21 changed files with 409 additions and 66 deletions.
23 changes: 14 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,28 @@
<artifact-registry.url>artifactregistry://europe-north1-maven.pkg.dev/artifact-registry-14da</artifact-registry.url>

<!-- Dependency versions -->
<assertj.version>3.23.1</assertj.version>
<assertj.version>3.24.2</assertj.version>
<format-preserving-encryption.version>1.0.0</format-preserving-encryption.version>
<guava.version>31.1-jre</guava.version>
<jackson.version>2.14.0</jackson.version>
<jackson.version>2.14.1</jackson.version>
<jsonassert.version>1.5.1</jsonassert.version>
<junit5.version>5.9.1</junit5.version>
<junit5.version>5.9.2</junit5.version>
<logback.version>1.4.5</logback.version>
<lombok.version>1.18.24</lombok.version>
<slf4j.version>1.7.36</slf4j.version>
<tink.version>1.7.0</tink.version>
<lombok.version>1.18.26</lombok.version>
<slf4j.version>2.0.6</slf4j.version>
<tink.version>1.8.0</tink.version>
<tink-fpe-java.version>0.0.1</tink-fpe-java.version>

<!-- Plugin/extension versions -->
<artifactregistry-maven-wagon.version>2.1.4</artifactregistry-maven-wagon.version>
<checkstyle.version>9.3</checkstyle.version>
<checkstyle.version>10.8.1</checkstyle.version>
<maven-checkstyle-plugin.version>3.1.2</maven-checkstyle-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-javadoc-plugin.version>3.3.0</maven-javadoc-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
<jacoco-maven-plugin.version>0.8.7</jacoco-maven-plugin.version>
<jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version>
<sonar-maven-plugin.version>3.9.1.2184</sonar-maven-plugin.version>
</properties>

Expand All @@ -58,7 +59,11 @@
<artifactId>tink</artifactId>
<version>${tink.version}</version>
</dependency>

<dependency>
<groupId>no.ssb.crypto.tink</groupId>
<artifactId>tink-fpe-java</artifactId>
<version>${tink-fpe-java.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class PseudoFuncConfig implements Serializable {
@Getter
private final String funcImpl;


private final Map<String, Object> config = new HashMap<>();

/** Construct from Map */
Expand Down Expand Up @@ -48,6 +47,10 @@ public <T> T getRequired(String paramName, Class<T> clazz) {
.orElseThrow(() -> new PseudoFuncMissingParamException(paramName));
}

public Map<String, Object> asMap() {
return Map.copyOf(config);
}

public void add(String paramName, Object o) {
config.put(paramName, o);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/no/ssb/dapla/dlp/pseudo/func/map/MapFunc.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
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;
import no.ssb.dapla.dlp.pseudo.func.PseudoFuncConfig;
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();
Expand All @@ -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 {
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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();
}

}
21 changes: 20 additions & 1 deletion src/main/java/no/ssb/dapla/dlp/pseudo/func/util/FromString.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class FromString {

private static Map<Class<?>, Function<String,?>> fromStringFunctionMap = ImmutableMap.<Class<?>, Function<String,?>>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))
Expand All @@ -36,8 +37,26 @@ private static String blankToNull(String s) {
return emptyToNull(nullToEmpty(s).trim());
}

private static <E extends Enum<E>> E convertEnum(String name, Class<E> 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> T convert(String data, Class<T> clazz) {
return (T) fromStringFunctionMap.get(clazz).apply(data);
if (clazz.isEnum()) {
return (T) convertEnum(data, (Class<Enum>) clazz);
}
else {
return (T) fromStringFunctionMap.get(clazz).apply(data);
}
}

}
Loading

0 comments on commit c9c4488

Please sign in to comment.