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

Add property based testing for encryption/decryption #425

Merged
merged 1 commit into from
Nov 4, 2022
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ generated_testSrc

# Mac
.DS_Store

# Jqwik property based testing
.jqwik-database
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ subprojects {

tasks.withType(Test) {
useJUnitPlatform {
includeEngines 'junit-jupiter'
includeEngines 'jqwik', 'junit-jupiter'
}
maxParallelForks Runtime.getRuntime().availableProcessors()
testLogging {
Expand Down
3 changes: 3 additions & 0 deletions encrypted-config-value-module/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ dependencies {
implementation 'com.palantir.safe-logging:preconditions'
implementation 'com.palantir.safe-logging:safe-logging'

testRuntimeOnly 'net.jqwik:jqwik'

testImplementation 'com.fasterxml.jackson.core:jackson-annotations'
testImplementation 'com.google.code.findbugs:jsr305'
testImplementation 'com.google.errorprone:error_prone_annotations'
testImplementation 'net.jqwik:jqwik-api'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,48 @@
import com.palantir.config.crypto.algorithm.Algorithm;
import com.palantir.config.crypto.util.StringSubstitutionException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import net.jqwik.api.Assume;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.constraints.CharRange;
import net.jqwik.api.constraints.StringLength;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class DecryptingVariableSubstitutorTest {
public final class DecryptingVariableSubstitutorTest {

private static final Algorithm ALGORITHM = Algorithm.RSA;
private static final KeyPair KEY_PAIR = ALGORITHM.newKeyPair();
public static final String TEST_KEY_PATH = DecryptingVariableSubstitutorTest.class.getName() + "test-key";
private static String previousProperty;

private final DecryptingVariableSubstitutor substitutor = new DecryptingVariableSubstitutor();

@BeforeAll
public static void beforeClass() throws IOException {
previousProperty = System.getProperty(KeyFileUtils.KEY_PATH_PROPERTY);

Path tempFilePath = Files.createTempDirectory("temp-key-directory").resolve("test.key");
KeyFileUtils.keyPairToFile(KEY_PAIR, tempFilePath);
System.setProperty(
KeyFileUtils.KEY_PATH_PROPERTY, tempFilePath.toAbsolutePath().toString());
ensureTestKeysExist();
}

@AfterAll
public static void afterClass() {
if (previousProperty != null) {
System.setProperty(KeyFileUtils.KEY_PATH_PROPERTY, previousProperty);
}
System.clearProperty(TEST_KEY_PATH);
}

@Test
public final void constantsAreNotModified() {
public void constantsAreNotModified() {
assertThat(substitutor.replace("abc")).isEqualTo("abc");
}

@Test
public final void invalidEncryptedVariablesThrowStringSubstitutionException() {
public void invalidEncryptedVariablesThrowStringSubstitutionException() {
try {
substitutor.replace("${enc:invalid-contents}");
fail("fail");
Expand All @@ -70,20 +74,60 @@ public final void invalidEncryptedVariablesThrowStringSubstitutionException() {
}

@Test
public final void nonEncryptedVariablesAreNotModified() {
public void nonEncryptedVariablesAreNotModified() {
assertThat(substitutor.replace("${abc}")).isEqualTo("${abc}");
}

@Test
public final void variableIsDecrypted() throws Exception {
public void variableIsDecrypted() {
assertThat(substitutor.replace("${" + encrypt("abc") + "}")).isEqualTo("abc");
}

@Test
public final void variableIsDecryptedWithRegex() throws Exception {
public void variableIsDecryptedWithRegex() {
assertThat(substitutor.replace("${" + encrypt("$5") + "}")).isEqualTo("$5");
}

@Test
public void decryptsMultiple() {
String abc = "${" + encrypt("abc") + "}";
String def = "${" + encrypt("def") + "}";
String hello = "${" + encrypt("enc:hello") + "}";
String source = abc + ":" + def + '.' + hello;
assertThat(substitutor.replace(source)).isEqualTo("abc:def.enc:hello");
}

@Test
public void decryptsWithPlaceholders() {
String abc = "${" + encrypt("abc") + "}";
String def = "${" + encrypt("${enc:test}") + "}";
String source = abc + ":" + def;
assertThat(substitutor.replace(source)).isEqualTo("abc:${enc:test}");
}

@Property(tries = 10_000)
void propertyTestValues(@ForAll @CharRange(from = 0, to = 1024) @StringLength(max = 100) String plaintext)
throws IOException {
// RSA test key can only encrypt 190 bytes
Assume.that(plaintext.getBytes(StandardCharsets.UTF_8).length <= 190);
ensureTestKeysExist();
assertThat(substitutor.replace("${" + encrypt(plaintext) + "}")).isEqualTo(plaintext);
}

private static void ensureTestKeysExist() throws IOException {
String testKeyPath = System.getProperty(TEST_KEY_PATH);
if (testKeyPath != null && Files.isRegularFile(Path.of(testKeyPath))) {
return;
}
Path tempFilePath = Files.createTempDirectory("temp-key-directory")
.resolve(ALGORITHM.name() + "-test.key")
.toAbsolutePath();
KeyFileUtils.keyPairToFile(KEY_PAIR, tempFilePath);
String path = tempFilePath.toString();
System.setProperty(KeyFileUtils.KEY_PATH_PROPERTY, path);
System.setProperty(TEST_KEY_PATH, path);
}

private String encrypt(String value) {
return ALGORITHM.newEncrypter().encrypt(KEY_PAIR.encryptionKey(), value).toString();
}
Expand Down
3 changes: 3 additions & 0 deletions encrypted-config-value/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ dependencies {
implementation 'com.palantir.safe-logging:safe-logging'
implementation 'com.palantir.safe-logging:preconditions'

testRuntimeOnly 'net.jqwik:jqwik'

testImplementation 'net.jqwik:jqwik-api'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,27 @@
import static org.assertj.core.api.Assertions.assertThat;

import com.palantir.config.crypto.algorithm.Algorithm;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import net.jqwik.api.Assume;
import net.jqwik.api.EdgeCasesMode;
import net.jqwik.api.ForAll;
import net.jqwik.api.Property;
import net.jqwik.api.constraints.StringLength;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public final class AlgorithmTest {
private static final String plaintext = "Some top secret plaintext for testing things";
static Stream<Arguments> args() {
return Stream.of(Algorithm.values())
.flatMap(algorithm -> Stream.of(
Arguments.of(algorithm, "Some top secret plaintext for testing things"),
Arguments.of(algorithm, "test")));
}

@ParameterizedTest
@EnumSource(Algorithm.class)
@MethodSource("args")
public void weGenerateRandomKeys(Algorithm algorithm) {
KeyPair keyPair1 = algorithm.newKeyPair();
KeyPair keyPair2 = algorithm.newKeyPair();
Expand All @@ -35,21 +48,14 @@ public void weGenerateRandomKeys(Algorithm algorithm) {
}

@ParameterizedTest
@EnumSource(Algorithm.class)
public void weCanEncryptAndDecrypt(Algorithm algorithm) {
KeyPair keyPair = algorithm.newKeyPair();

EncryptedValue encryptedValue = algorithm.newEncrypter().encrypt(keyPair.encryptionKey(), plaintext);

KeyWithType decryptionKey = keyPair.decryptionKey();
String decrypted = encryptedValue.decrypt(decryptionKey);

assertThat(decrypted).isEqualTo(plaintext);
@MethodSource("args")
public void weCanEncryptAndDecrypt(Algorithm algorithm, String plaintext) {
encryptAndDecrypt(algorithm, plaintext);
}

@ParameterizedTest
@EnumSource(Algorithm.class)
public void theSameStringEncryptsToDifferentCiphertexts(Algorithm algorithm) {
@MethodSource("args")
public void theSameStringEncryptsToDifferentCiphertexts(Algorithm algorithm, String plaintext) {
KeyPair keyPair = algorithm.newKeyPair();

EncryptedValue encrypted1 = algorithm.newEncrypter().encrypt(keyPair.encryptionKey(), plaintext);
Expand All @@ -68,4 +74,27 @@ public void theSameStringEncryptsToDifferentCiphertexts(Algorithm algorithm) {
assertThat(decryptedString1).isEqualTo(plaintext);
assertThat(decryptedString2).isEqualTo(plaintext);
}

@Property(tries = 10_000)
void aes(@ForAll @StringLength(max = 100_000) String plaintext) {
encryptAndDecrypt(Algorithm.AES, plaintext);
}

@Property(tries = 100, edgeCases = EdgeCasesMode.MIXIN)
void rsa(@ForAll @StringLength(max = 64) String plaintext) {
// RSA test key can only encrypt 190 bytes
Assume.that(plaintext.getBytes(StandardCharsets.UTF_8).length <= 190);
encryptAndDecrypt(Algorithm.RSA, plaintext);
}

private static void encryptAndDecrypt(Algorithm algorithm, String plaintext) {
KeyPair keyPair = algorithm.newKeyPair();

EncryptedValue encryptedValue = algorithm.newEncrypter().encrypt(keyPair.encryptionKey(), plaintext);

KeyWithType decryptionKey = keyPair.decryptionKey();
String decrypted = encryptedValue.decrypt(decryptionKey);

assertThat(decrypted).isEqualTo(plaintext);
}
}
13 changes: 9 additions & 4 deletions versions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ io.dropwizard:dropwizard-testing:1.3.29 (1 constraints: 0305f035)
junit:junit:4.13.1 (3 constraints: ed4b45b4)
net.bytebuddy:byte-buddy:1.12.16 (2 constraints: ef164566)
net.bytebuddy:byte-buddy-agent:1.12.16 (1 constraints: 750baee9)
org.apiguardian:apiguardian-api:1.1.2 (5 constraints: 105480ac)
net.jqwik:jqwik:1.7.0 (1 constraints: 0a050536)
net.jqwik:jqwik-api:1.7.0 (5 constraints: 5c2915b3)
net.jqwik:jqwik-engine:1.7.0 (1 constraints: 9b07f76c)
net.jqwik:jqwik-time:1.7.0 (1 constraints: 9b07f76c)
net.jqwik:jqwik-web:1.7.0 (1 constraints: 9b07f76c)
org.apiguardian:apiguardian-api:1.1.2 (10 constraints: 4f819858)
org.assertj:assertj-core:3.23.1 (2 constraints: 0614c776)
org.glassfish.jersey.test-framework:jersey-test-framework-core:2.25.1 (1 constraints: aa230902)
org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-inmemory:2.25.1 (1 constraints: c90ee65f)
Expand All @@ -114,9 +119,9 @@ org.junit.jupiter:junit-jupiter:5.9.1 (1 constraints: 11052036)
org.junit.jupiter:junit-jupiter-api:5.9.1 (5 constraints: 4f4322b4)
org.junit.jupiter:junit-jupiter-engine:5.9.1 (1 constraints: 0c0ee13b)
org.junit.jupiter:junit-jupiter-params:5.9.1 (2 constraints: 1c13903c)
org.junit.platform:junit-platform-commons:1.9.1 (2 constraints: dd200f4b)
org.junit.platform:junit-platform-engine:1.9.1 (1 constraints: ab1029b4)
org.junit.platform:junit-platform-commons:1.9.1 (4 constraints: 2134e7af)
org.junit.platform:junit-platform-engine:1.9.1 (2 constraints: eb1a8a4e)
org.mockito:mockito-core:4.8.1 (2 constraints: d6130a66)
org.mockito:mockito-junit-jupiter:4.8.1 (1 constraints: 0f051836)
org.objenesis:objenesis:3.3 (3 constraints: 8e1d111c)
org.opentest4j:opentest4j:1.2.0 (2 constraints: cd205b49)
org.opentest4j:opentest4j:1.2.0 (6 constraints: 72469adf)
1 change: 1 addition & 0 deletions versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ com.palantir.safe-logging:* = 3.2.0
io.dropwizard:* = 1.0.0
io.dropwizard:dropwizard-validation = 1.3.29
javax.ws.rs:javax.ws.rs-api = 2.0.1
net.jqwik:* = 1.7.0
org.assertj:assertj-core = 3.23.1
org.immutables:* = 2.8.8
org.junit.jupiter:* = 5.9.1
Expand Down