Skip to content

Commit

Permalink
Merge pull request #3 from certifaction/encryption-key-validation
Browse files Browse the repository at this point in the history
Encryption key validation
  • Loading branch information
MLukman authored Feb 6, 2025
2 parents adc8a29 + 3d037b4 commit 17cb62d
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 11 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build and Release JAR

on:
push:
tags:
- 'v*'

env:
KEYCLOAK_VERSION: 25.0.6

jobs:
build-and-release:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

- name: Install Git
run: sudo apt-get update && sudo apt-get install -y git

- name: Build with Maven
run: mvn clean package -Dkeycloak.version=$KEYCLOAK_VERSION -Drevision=${{ github.ref_name }}
env:
KEYCLOAK_VERSION: ${{ env.KEYCLOAK_VERSION }}

- name: Upload JAR artifact
uses: actions/upload-artifact@v4
with:
name: pii-encryption-provider
path: target/*.jar

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: target/*.jar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ FROM maven:3-openjdk-17-slim AS provider-pii
ARG KEYCLOAK_VERSION
WORKDIR /app
COPY pom.xml .
RUN mvn verify --fail-never -Dkeycloak.version=$KEYCLOAK_VERSION
RUN mvn verify -B -Dkeycloak.version=$KEYCLOAK_VERSION
COPY src ./src
RUN mvn package -o -Dkeycloak.version=$KEYCLOAK_VERSION
RUN mvn test package -B -Dkeycloak.version=$KEYCLOAK_VERSION

### Build customized Keycloak

Expand Down
38 changes: 38 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<maven.compiler.target>17</maven.compiler.target>
<keycloak.version>25.0.1</keycloak.version>
<netbeans.hint.jdkPlatform>JDK_17</netbeans.hint.jdkPlatform>
<junit.jupiter.version>5.9.2</junit.jupiter.version>
</properties>
<dependencies>
<dependency>
Expand All @@ -20,5 +21,42 @@
<version>${keycloak.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit-platform</artifactId>
<version>3.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-jupiter</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<!-- Tells the JVM when running tests to open java.util for reflection by unnamed modules. Required by system-stubs-jupiter -->
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package my.unifi.eset.keycloak.piidataencryption.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
Expand Down Expand Up @@ -107,19 +108,41 @@ public static boolean isEncryptedValue(String value) {
}

static synchronized SecretKey getEncryptionKey() throws NoSuchAlgorithmException {
if (key == null) {
String rawkey = System.getenv("KC_PII_ENCKEY");
if (rawkey == null || rawkey.isBlank()) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(System.getenv("KC_DB_URL").getBytes());
rawkey = HexFormat.of().formatHex(md.digest()).toLowerCase();
Logger.getLogger(EncryptionUtils.class).warnf("Encryption key generated using MD5 hash of KC_DB_URL envvar is %s. It is recommended to set this key as KC_PII_ENCKEY envvar.", rawkey);
}
key = new SecretKeySpec(rawkey.getBytes(), "AES");
if(key != null){
return key;
}

String rawkey = System.getenv("KC_PII_ENCKEY");
if (rawkey == null || rawkey.isBlank()) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(System.getenv("KC_DB_URL").getBytes());
rawkey = HexFormat.of().formatHex(md.digest()).toLowerCase();
Logger.getLogger(EncryptionUtils.class).warn("Encryption key generated using MD5 hash of KC_DB_URL. It is recommended to set this key as KC_PII_ENCKEY envvar.");
}

SecretKeySpec genKey = new SecretKeySpec(rawkey.getBytes(), "AES");
try {
validateKey(genKey);
} catch (IllegalArgumentException e) {
throw e;
}

key = genKey;

return key;
}

public static void validateKey(SecretKeySpec candidateKey) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, candidateKey, new IvParameterSpec(new byte[16]));
// Trivial encryption to validate
cipher.doFinal("test".getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new IllegalArgumentException("Invalid encryption key for algorithm " + algorithm, e);
}
}

private EncryptionUtils() {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package my.unifi.eset.keycloak.piidataencryption.utils;

import org.junit.jupiter.api.BeforeEach;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

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.security.NoSuchAlgorithmException;

import static my.unifi.eset.keycloak.piidataencryption.utils.EncryptionUtils.algorithm;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(SystemStubsExtension.class)
class EncryptionUtilsTest {

@SystemStub
private EnvironmentVariables environmentVariables;

protected final String envVarKey = "KC_PII_ENCKEY";
protected final String validEncKey = "1234567891123456";

@BeforeEach
void setUp() {
EncryptionUtils.key = null;

environmentVariables.set(envVarKey, validEncKey);
}

@Test
void testEncryptValue() throws NoSuchAlgorithmException {
String encryptedValue = EncryptionUtils.encryptValue("test");
assertNotEquals("test", encryptedValue);

String decryptedValue = EncryptionUtils.decryptValue(encryptedValue);
assertEquals("test", decryptedValue);
}

@Test
void testDecryptValue() throws NoSuchAlgorithmException {
SecretKey key = EncryptionUtils.getEncryptionKey();

String decryptedValue = EncryptionUtils.decryptValue("$$$GTaogsGC8vbgE098AN9kC+UCHD8vYzVgFF0hFDnuKIw=");

assertEquals("test", decryptedValue);
}

@Test
void testWrongKeySize() {
environmentVariables.set(envVarKey, "invalid");

Throwable thrown = assertThrows(RuntimeException.class, () -> {
EncryptionUtils.getEncryptionKey();
});

assertEquals("Invalid encryption key for algorithm " + algorithm, thrown.getMessage());


environmentVariables.set(envVarKey, validEncKey);
assertDoesNotThrow(() -> EncryptionUtils.getEncryptionKey(), "should work once the key is correct and not reuse the previous one");
}

@Test
void testValidKey() {
byte[] validKeyBytes = validEncKey.getBytes();
SecretKeySpec validKey = new SecretKeySpec(validKeyBytes, "AES");

assertDoesNotThrow(() -> EncryptionUtils.validateKey(validKey));
}

@Test
void testValidateAnInvalidKey() {
byte[] invalidKeyBytes = "invalid_size".getBytes();
SecretKeySpec invalidKey = new SecretKeySpec(invalidKeyBytes, "AES");

IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
EncryptionUtils.validateKey(invalidKey);
});

String expectedMessage = "Invalid encryption key for algorithm " + algorithm;
assertThrows(IllegalArgumentException.class, () -> EncryptionUtils.validateKey(invalidKey));
assert(thrown.getMessage().contains(expectedMessage));
}
}

0 comments on commit 17cb62d

Please sign in to comment.