diff --git a/src/main/java/io/github/tap30/hiss/key/KeyHashGenerator.java b/src/main/java/io/github/tap30/hiss/key/KeyHashGenerator.java index b6db505..d5ec6b1 100644 --- a/src/main/java/io/github/tap30/hiss/key/KeyHashGenerator.java +++ b/src/main/java/io/github/tap30/hiss/key/KeyHashGenerator.java @@ -34,7 +34,6 @@ public void generateAndLogHashes(Collection keys) { } /** - * @param keys * @return map of key ID to key hash. */ public Map generateHashes(Collection keys) { @@ -44,12 +43,20 @@ public Map generateHashes(Collection keys) { } /** - * @param keys * @return invalid key IDs. */ public Set validateKeyHashes(Collection keys) { return keys.stream() - .filter(key -> StringUtils.hasText(key.getKeyHash())) + .filter(key -> { + if (StringUtils.hasText(key.getKeyHash())) { + return true; + } else { + logger.log(Level.WARNING, + "Key {0} does not have hash; supply it as soon as possible.", + key.getId()); + return false; + } + }) .filter(key -> !verifyer.verify(key.getKey(), key.getKeyHash().getBytes(CHARSET)).verified) .map(Key::getId) .collect(Collectors.toSet()); diff --git a/src/main/java/io/github/tap30/hiss/properties/HissPropertiesFromEnvProvider.java b/src/main/java/io/github/tap30/hiss/properties/HissPropertiesFromEnvProvider.java index 81c7407..d7d6de3 100644 --- a/src/main/java/io/github/tap30/hiss/properties/HissPropertiesFromEnvProvider.java +++ b/src/main/java/io/github/tap30/hiss/properties/HissPropertiesFromEnvProvider.java @@ -9,6 +9,7 @@ import java.util.function.Supplier; // Todo: improve doc + /** * Sample Envs: *
diff --git a/src/test/java/io/github/tap30/hissapp/Application.java b/src/test/java/io/github/tap30/hissapp/Application.java new file mode 100644 index 0000000..62a12d4 --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/Application.java @@ -0,0 +1,386 @@ +package io.github.tap30.hissapp; + +import io.github.tap30.hiss.Hiss; +import io.github.tap30.hiss.HissFactory; +import io.github.tap30.hiss.properties.HissProperties; +import io.github.tap30.hissapp.model.Address; +import io.github.tap30.hissapp.model.Admin; +import io.github.tap30.hissapp.model.Secret; +import io.github.tap30.hissapp.model.User; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SystemStubsExtension.class) +public class Application { + + @SystemStub + static EnvironmentVariables environment = new EnvironmentVariables( + "HISS_DEFAULT_ENCRYPTION_KEY_ID", "default_key", + "HISS_DEFAULT_ENCRYPTION_ALGORITHM", "aes-128-gcm", + "HISS_DEFAULT_HASHING_KEY_ID", "default_key", + "HISS_DEFAULT_HASHING_ALGORITHM", "hmac-sha256", + "HISS_KEYS_DEFAULT_KEY", "AAAAAAAAAAAAAAAAAAAAAA==", + "HISS_KEYS_DEFAULT_KEY___HASH", "$2a$12$3T0VMnGMgvesehYomommnO02dbFOJuM/3elsmgmsB2/qlGSF3BIbe", + "HISS_KEYS_OLD_KEY", "AQIDBAUGBwgJCgsMDQ4PEA==", + "HISS_KEYS_OLD_KEY___HASH", "$2a$12$THkoYZHlqD/HvrSkKUDs9eyHwY7W2FmyJm6SMp4xeGfP2g7F6Ro/i", + "HISS_KEY_HASH_GENERATION_ENABLED", "true" + ); + + private static Hiss hiss; + + @BeforeAll + static void setUpHiss() { + hiss = HissFactory.createHiss(HissProperties.fromEnv()); + } + + @Test + void encryptUser() { + // Given + var user = User.builder() + .id("user-01") + .name("Mostafa") + .phoneNumber("+989123456789") + .addresses(List.of( + Address.builder() + .name("home") + .city("Tehran") + .street("Enghelab") + .postalCode("1234567890") + .build(), + Address.builder() + .name("work") + .city("Mashhad") + .street("Azadi") + .postalCode("1234567891") + .build() + )) + .build(); + + // When + hiss.encryptObject(user); + + // Then + assertEquals("user-01", user.getId()); + assertEquals("Mostafa", user.getName()); + assertTrue(user.getPhoneNumber().startsWith("+98912345")); + assertFalse(user.getPhoneNumber().contains("6789")); + assertEquals(2, user.getAddresses().size()); + { + var address = user.getAddresses().get(0); + assertEquals("home", address.getName()); + assertEquals("Tehran", address.getCity()); + assertFalse(address.getStreet().contains("Enghelab")); + assertFalse(address.getPostalCode().contains("1234567890")); + } + { + var address = user.getAddresses().get(1); + assertEquals("work", address.getName()); + assertEquals("Mashhad", address.getCity()); + assertFalse(address.getStreet().contains("Azadi")); + assertFalse(address.getPostalCode().contains("1234567891")); + } + + // When + hiss.decryptObject(user); + + + // Then + assertEquals("user-01", user.getId()); + assertEquals("Mostafa", user.getName()); + assertEquals("+989123456789", user.getPhoneNumber()); + assertEquals(2, user.getAddresses().size()); + { + var address = user.getAddresses().get(0); + assertEquals("home", address.getName()); + assertEquals("Tehran", address.getCity()); + assertEquals("Enghelab", address.getStreet()); + assertEquals("1234567890", address.getPostalCode()); + } + { + var address = user.getAddresses().get(1); + assertEquals("work", address.getName()); + assertEquals("Mashhad", address.getCity()); + assertEquals("Azadi", address.getStreet()); + assertEquals("1234567891", address.getPostalCode()); + } + } + + @Test + void rotateUserAlgorithmAndKeyId() { + // Given + var user = User.builder() + .id("user-01") + .name("Mostafa") + .phoneNumber("+98912345#$$#{aes-128-cbc:old_key}{/UjgtjsLutCrcYTJ/APxVTu0CLjWlElScSgBw7IGcwY=}#$$#") + .addresses(List.of( + Address.builder() + .name("home") + .city("Tehran") + .street("#$$#{aes-128-cbc:old_key}{qhcgSbNLHfWzJS1vIUC23xdZWDdc6/L8RSf9nArTGN8=}#$$#") + .postalCode("#$$#{aes-128-cbc:old_key}{PjjcFEuslRRhP39s8oOYNGIxL8ta2748wpVu4SZkiBo=}#$$#") + .build(), + Address.builder() + .name("work") + .city("Mashhad") + .street("#$$#{aes-128-cbc:old_key}{anBz1DfneYckkZ2/Esh9MybXh0xWJIT0SCEBI9RtWoc=}#$$#") + .postalCode("#$$#{aes-128-cbc:old_key}{UtTWbxJTqRDcHRebijQpVROSN0NAgwR3qETjSSVKDyA=}#$$#") + .build() + )) + .build(); + + // When + hiss.decryptObject(user); + + + // Then + assertEquals("user-01", user.getId()); + assertEquals("Mostafa", user.getName()); + assertEquals("+989123456789", user.getPhoneNumber()); + assertEquals(2, user.getAddresses().size()); + { + var address = user.getAddresses().get(0); + assertEquals("home", address.getName()); + assertEquals("Tehran", address.getCity()); + assertEquals("Enghelab", address.getStreet()); + assertEquals("1234567890", address.getPostalCode()); + } + { + var address = user.getAddresses().get(1); + assertEquals("work", address.getName()); + assertEquals("Mashhad", address.getCity()); + assertEquals("Azadi", address.getStreet()); + assertEquals("1234567891", address.getPostalCode()); + } + } + + @Test + void encryptAdmin() { + // Given + var admin = new Admin(); + admin.setId("admin-01"); + admin.setName("Mostafa"); + admin.setPhoneNumber("+989123456789"); + admin.setAddresses(List.of( + Address.builder() + .name("home") + .city("Tehran") + .street("Enghelab") + .postalCode("1234567890") + .build(), + Address.builder() + .name("work") + .city("Mashhad") + .street("Azadi") + .postalCode("1234567891") + .build() + )); + admin.setSecrets(Map.of( + "secret-01", Secret.builder() + .secret("secret number one") + .applications(Set.of("app1", "app2")) + .build(), + "secret-02", Secret.builder() + .secret("secret number two") + .applications(Set.of("app3", "app4")) + .build() + )); + + // When + hiss.encryptObject(admin); + + // Then + assertEquals("admin-01", admin.getId()); + assertEquals("Mostafa", admin.getName()); + assertTrue(admin.getPhoneNumber().startsWith("+98912345")); + assertFalse(admin.getPhoneNumber().contains("6789")); + assertEquals(2, admin.getAddresses().size()); + { + var address = admin.getAddresses().get(0); + assertEquals("home", address.getName()); + assertEquals("Tehran", address.getCity()); + assertFalse(address.getStreet().contains("Enghelab")); + assertFalse(address.getPostalCode().contains("1234567890")); + } + { + var address = admin.getAddresses().get(1); + assertEquals("work", address.getName()); + assertEquals("Mashhad", address.getCity()); + assertFalse(address.getStreet().contains("Azadi")); + assertFalse(address.getPostalCode().contains("1234567891")); + } + assertEquals(2, admin.getSecrets().size()); + { + var secret = admin.getSecrets().get("secret-01"); + assertFalse(secret.getSecret().contains("secret number one")); + assertEquals(Set.of("app1", "app2"), secret.getApplications()); + } + { + var secret = admin.getSecrets().get("secret-02"); + assertFalse(secret.getSecret().contains("secret number two")); + assertEquals(Set.of("app3", "app4"), secret.getApplications()); + } + + // When + hiss.decryptObject(admin); + + + // Then + assertEquals("admin-01", admin.getId()); + assertEquals("Mostafa", admin.getName()); + assertEquals("+989123456789", admin.getPhoneNumber()); + + assertEquals(2, admin.getAddresses().size()); + { + var address = admin.getAddresses().get(0); + assertEquals("home", address.getName()); + assertEquals("Tehran", address.getCity()); + assertEquals("Enghelab", address.getStreet()); + assertEquals("1234567890", address.getPostalCode()); + } + { + var address = admin.getAddresses().get(1); + assertEquals("work", address.getName()); + assertEquals("Mashhad", address.getCity()); + assertEquals("Azadi", address.getStreet()); + assertEquals("1234567891", address.getPostalCode()); + } + assertEquals(2, admin.getSecrets().size()); + { + var secret = admin.getSecrets().get("secret-01"); + assertEquals("secret number one", secret.getSecret()); + assertEquals(Set.of("app1", "app2"), secret.getApplications()); + } + { + var secret = admin.getSecrets().get("secret-02"); + assertEquals("secret number two", secret.getSecret()); + assertEquals(Set.of("app3", "app4"), secret.getApplications()); + } + } + + @Test + void encryptString() { + // Given + final var text = "plain text"; + + // When + var encryptedText = hiss.encrypt(text); + encryptedText = hiss.encrypt(encryptedText); + encryptedText = hiss.encrypt(encryptedText); + + // Then + assertFalse(encryptedText.contains(text)); + + // When + var decryptedText = hiss.decrypt(encryptedText); + decryptedText = hiss.decrypt(decryptedText); + decryptedText = hiss.decrypt(decryptedText); + + // Then + assertEquals(text, decryptedText); + } + + @Test + void encryptString_withPattern() { + // Given + final var text = "Your code is 12345. Keep it safe."; + + // When + var encryptedText = hiss.encrypt(text, "\\d+"); + encryptedText = hiss.encrypt(encryptedText, "\\d+"); + encryptedText = hiss.encrypt(encryptedText, "\\d+"); + + // Then + assertFalse(encryptedText.contains("12345")); + assertTrue(encryptedText.startsWith("Your code is ")); + assertTrue(encryptedText.endsWith(". Keep it safe.")); + + // When + var decryptedText = hiss.decrypt(encryptedText); + decryptedText = hiss.decrypt(decryptedText); + decryptedText = hiss.decrypt(decryptedText); + + // Then + assertEquals(text, decryptedText); + } + + @Test + void decryptString() { + // Given + var encryptedText = "#$$#{aes-128-gcm:default_key}{dZdE50gZRAtgzQ9ar2hemaWg0flEL9/SO8CoaZ+K12u6mDirOSaIeA==}#$$#"; + + // When + var decryptedText = hiss.decrypt(encryptedText); + + // Then + assertEquals("Enghelab", decryptedText); + } + + @Test + void decryptString_withPattern() { + // Given + var encryptedText = "+98912345#$$#{aes-128-gcm:default_key}{ha8e/UDZmsLuAqGW9zGo7eaq5e8cM79OO7Mp2ZUThcup2+8O}#$$#"; + + // When + var decryptedText = hiss.decrypt(encryptedText); + + // Then + assertEquals("+989123456789", decryptedText); + } + + @Test + void decryptString_withKeyIdAndAlgorithmDifferentFromDefaults() { + // Given + var encryptedText = "#$$#{aes-128-cbc:old_key}{UtTWbxJTqRDcHRebijQpVROSN0NAgwR3qETjSSVKDyA=}#$$#"; + + // When + var decryptedText = hiss.decrypt(encryptedText); + + // Then + assertEquals("1234567891", decryptedText); + } + + @Test + void hashString() { + // Given + final var text = "plain text"; + + // When + var hashedText1 = hiss.hash(text); + var hashedText2 = hiss.hash(hashedText1); + var hashedText3 = hiss.hash(hashedText1); + + // Then + assertEquals(hashedText1, hashedText2); + assertEquals(hashedText2, hashedText3); + assertFalse(hashedText1.contains(text)); + } + + @Test + void hashString_withPattern() { + // Given + final var text = "Your code is 12345. Keep it safe."; + + // When + var hashedText1 = hiss.hash(text, "\\d+"); + var hashedText2 = hiss.hash(hashedText1, "\\d+"); + var hashedText3 = hiss.hash(hashedText2, "\\d+"); + + // Then + assertEquals(hashedText1, hashedText2); + assertEquals(hashedText2, hashedText3); + assertFalse(hashedText1.contains("12345")); + assertTrue(hashedText1.startsWith("Your code is ")); + assertTrue(hashedText1.endsWith(". Keep it safe.")); + } + +} diff --git a/src/test/java/io/github/tap30/hissapp/ApplicationWithInvalidKeyHashes.java b/src/test/java/io/github/tap30/hissapp/ApplicationWithInvalidKeyHashes.java new file mode 100644 index 0000000..d61f76d --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/ApplicationWithInvalidKeyHashes.java @@ -0,0 +1,26 @@ +package io.github.tap30.hissapp; + +import io.github.tap30.hiss.HissFactory; +import io.github.tap30.hiss.properties.HissProperties; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SystemStubsExtension.class) +public class ApplicationWithInvalidKeyHashes { + + @SystemStub + EnvironmentVariables environment = new EnvironmentVariables(Application.environment.getVariables()) + .set("HISS_KEYS_DEFAULT_KEY___HASH", "bad hash") + .set("HISS_KEYS_OLD_KEY___HASH", "bad hash"); + + @Test + void createHiss() { + assertThrows(IllegalArgumentException.class, () -> HissFactory.createHiss(HissProperties.fromEnv())); + } + +} diff --git a/src/test/java/io/github/tap30/hissapp/model/Address.java b/src/test/java/io/github/tap30/hissapp/model/Address.java new file mode 100644 index 0000000..6909e0d --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/model/Address.java @@ -0,0 +1,26 @@ +package io.github.tap30.hissapp.model; + +import io.github.tap30.hiss.Encrypted; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Address { + + private String name; + + private String city; + + @Encrypted(hashingEnabled = false) + private String street; + + @Encrypted + private String postalCode; + private String hashedPostalCode; + +} diff --git a/src/test/java/io/github/tap30/hissapp/model/Admin.java b/src/test/java/io/github/tap30/hissapp/model/Admin.java new file mode 100644 index 0000000..638c0ee --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/model/Admin.java @@ -0,0 +1,14 @@ +package io.github.tap30.hissapp.model; + +import io.github.tap30.hiss.EncryptedInside; +import lombok.Data; + +import java.util.Map; + +@Data +public class Admin extends User { + + @EncryptedInside + private Map secrets; + +} diff --git a/src/test/java/io/github/tap30/hissapp/model/Secret.java b/src/test/java/io/github/tap30/hissapp/model/Secret.java new file mode 100644 index 0000000..7277dd7 --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/model/Secret.java @@ -0,0 +1,22 @@ +package io.github.tap30.hissapp.model; + +import io.github.tap30.hiss.Encrypted; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Secret { + + @Encrypted(hashingEnabled = false) + private String secret; + + private Set applications; + +} diff --git a/src/test/java/io/github/tap30/hissapp/model/User.java b/src/test/java/io/github/tap30/hissapp/model/User.java new file mode 100644 index 0000000..9068ff4 --- /dev/null +++ b/src/test/java/io/github/tap30/hissapp/model/User.java @@ -0,0 +1,29 @@ +package io.github.tap30.hissapp.model; + +import io.github.tap30.hiss.Encrypted; +import io.github.tap30.hiss.EncryptedInside; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class User { + + private String id; + + private String name; + + @Encrypted(pattern = "\\d{4}$") + private String phoneNumber; + private String hashedPhoneNumber; + + @EncryptedInside + private List
addresses; + +}