Skip to content
This repository was archived by the owner on Aug 12, 2023. It is now read-only.
Open
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
34 changes: 34 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Docker
on:
push:
schedule:
- cron: '54 2 2 * *'
workflow_dispatch:
jobs:
buildDockerImage:
name: Build Docker image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/wisvch/feedback-tool
tags: type=sha, prefix={{date 'YYYYMMDD'}}-
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
push: ${{ github.ref == 'refs/heads/master' }}
29 changes: 0 additions & 29 deletions .travis.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM wisvch/openjdk:11-jdk AS builder
FROM openjdk:11-jdk-buster AS builder
COPY . /src
WORKDIR /src
RUN ./gradlew build
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ dependencies {
compile 'org.projectlombok:lombok'
compile 'org.json:json:20180813'

compile 'org.apache.commons:commons-lang3:3.7'
runtime 'org.postgresql:postgresql'
runtime 'com.h2database:h2'

Expand Down
202 changes: 202 additions & 0 deletions src/main/java/ch/wisv/converters/AbstractCryptoConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package ch.wisv.converters;

import static ch.wisv.converters.KeyProperty.DATABASE_ENCRYPTION_KEY;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.persistence.AttributeConverter;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

/**
* AbstractCryptoConverter class.
*
* @param <T>
*/
abstract class AbstractCryptoConverter<T> implements AttributeConverter<T, String> {

/**
* Used for concatenating cipher text and iv together.
* This will not cause issues when the text contains hashtags,
* since a Base64 encoded string cannot contain hashtags.
* https://en.wikipedia.org/wiki/Base64#Base64_table
*/
private static final String CONCATENATION = "####";

/** CipherInitializer. */
private CipherInitializer cipherInitializer;

/**
* AbstractCryptoConverter constructor.
*/
public AbstractCryptoConverter() {
this(new CipherInitializer());
}

/**
* AbstractCryptoConverter constructor.
*
* @param cipherInitializer of type CipherInitializer
*/
public AbstractCryptoConverter(CipherInitializer cipherInitializer) {
this.cipherInitializer = cipherInitializer;
}

/**
* Convert th entity attribute to the database data.
*
* @param attribute of type T
*
* @return String
*/
@Override
public String convertToDatabaseColumn(T attribute) {
if (isNotEmpty(DATABASE_ENCRYPTION_KEY) && isNotNullOrEmpty(attribute)) {
try {
Cipher cipher = cipherInitializer.prepareAndInitCipher(Cipher.ENCRYPT_MODE, DATABASE_ENCRYPTION_KEY, null);

return this.concatenatedCipherTextAndIv(this.encrypt(cipher, attribute), cipher.getIV());
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException |
NoSuchPaddingException | IllegalBlockSizeException e) {
throw new RuntimeException(e);
}
}

return this.entityAttributeToString(attribute);
}

/**
* Convert the database data to the entity attribute.
*
* @param dbData of type String.
*
* @return T
*/
@Override
public T convertToEntityAttribute(String dbData) {
if (isNotEmpty(DATABASE_ENCRYPTION_KEY) && isNotEmpty(dbData)) {
try {
Pair<String, byte[]> cipherTextAndIv = this.splitDbData(dbData);
Cipher cipher = cipherInitializer.prepareAndInitCipher(Cipher.DECRYPT_MODE, DATABASE_ENCRYPTION_KEY, cipherTextAndIv.getRight());

return this.decrypt(cipher, cipherTextAndIv.getLeft());
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException |
NoSuchPaddingException | IllegalBlockSizeException e) {
throw new RuntimeException(e);
}
}

return this.stringToEntityAttribute(dbData);
}

/**
* Concatenated cipher text and IV together.
*
* @param cipherText of type String
* @param iv of type byte[]
*
* @return String
*/
private String concatenatedCipherTextAndIv(String cipherText, byte[] iv) {
return cipherText + CONCATENATION + Base64.getEncoder().encodeToString(iv);
}

/**
* Split db data into cipher text and IV.
*
* @param dbData of type String
*
* @return Pair
*/
private Pair<String, byte[]> splitDbData(String dbData) {
String[] splitDbData = dbData.split(CONCATENATION);
String cipherText = splitDbData[0];
byte[] iv = Base64.getDecoder().decode(splitDbData[1]);

return new ImmutablePair<>(cipherText, iv);
}

/**
* Do final Cipher call.
*
* @param cipher of type String
* @param bytes of type byte[]
*
* @return byte[]
*
* @throws IllegalBlockSizeException when the block size is wrong
* @throws BadPaddingException when the padding is wrong
*/
private byte[] callCipherDoFinal(Cipher cipher, byte[] bytes) throws IllegalBlockSizeException, BadPaddingException {
return cipher.doFinal(bytes);
}

/**
* Decrypt database data with a give cipher.
*
* @param cipher of type Cipher
* @param dbData of type String
*
* @return T
*
* @throws IllegalBlockSizeException when the block size is wrong
* @throws BadPaddingException when the padding is wrong
*/
private T decrypt(Cipher cipher, String dbData) throws IllegalBlockSizeException, BadPaddingException {
byte[] encryptedBytes = Base64.getDecoder().decode(dbData);
byte[] decryptedBytes = this.callCipherDoFinal(cipher, encryptedBytes);

return this.stringToEntityAttribute(new String(decryptedBytes));
}

/**
* Encrypt attribute with a give cipher.
*
* @param cipher of type Cipher
* @param attribute of type T
*
* @return String
*
* @throws IllegalBlockSizeException when the block size is wrong
* @throws BadPaddingException when the padding is wrong
*/
private String encrypt(Cipher cipher, T attribute) throws IllegalBlockSizeException, BadPaddingException {
byte[] bytesToEncrypt = this.entityAttributeToString(attribute).getBytes();
byte[] encryptedBytes = this.callCipherDoFinal(cipher, bytesToEncrypt);

return Base64.getEncoder().encodeToString(encryptedBytes);
}

/**
* Check attribute is not null or empty.
*
* @param attribute of type T
*
* @return boolean
*/
abstract boolean isNotNullOrEmpty(T attribute);

/**
* Convert database data to entity attribute.
*
* @param dbData of type String
*
* @return T
*/
abstract T stringToEntityAttribute(String dbData);

/**
* Convert entity attribute to database data.
*
* @param attribute of type T
*
* @return String
*/
abstract String entityAttributeToString(T attribute);
}
67 changes: 67 additions & 0 deletions src/main/java/ch/wisv/converters/CipherInitializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ch.wisv.converters;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
* CipherInitializer.
*/
class CipherInitializer {

/** Encryption method. */
private static final String CIPHER_INSTANCE_NAME = "AES/CBC/PKCS5Padding";

/** Secret key algorithm. */
private static final String SECRET_KEY_ALGORITHM = "AES";

/**
* Creates an IvParameterSpec object using the bytes in iv as the IV.
* The IV is fixed such that we do not have to save the IV.
* TODO: Random IV to improve security.
*
* @param cipher of type Cipher
*
* @return AlgorithmParameterSpec
*/
private AlgorithmParameterSpec getRandomIv(Cipher cipher) {
SecureRandom randomSecureRandom = new SecureRandom();
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);

return new IvParameterSpec(iv);
}

/**
* Prepare Cipher by adding the key and creating an IV.
*
* @param encryptionMode of type int
* @param key of type String
* @param iv of type byte[]
*
* @return Cipher
*
* @throws InvalidKeyException when key is invalid
* @throws NoSuchPaddingException when padding method does not exists
* @throws NoSuchAlgorithmException when the algorithm does not exists
* @throws InvalidAlgorithmParameterException when the mode of operation does not exists
*/
Cipher prepareAndInitCipher(int encryptionMode, String key, byte[] iv)
throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
Key secretKey = new SecretKeySpec(key.getBytes(), SECRET_KEY_ALGORITHM);

AlgorithmParameterSpec algorithmParameters = iv == null ? this.getRandomIv(cipher) : new IvParameterSpec(iv);
cipher.init(encryptionMode, secretKey, algorithmParameters);

return cipher;
}
}
23 changes: 23 additions & 0 deletions src/main/java/ch/wisv/converters/KeyProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ch.wisv.converters;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
* KeyProperty component.
*/
@Component
public class KeyProperty {

/** Encryption key. */
static String DATABASE_ENCRYPTION_KEY;

/**
* Set encryption key.
*/
@Value("${wisvch.database.encryption.key}")
public void setDatabase(String databaseEncryptionKey) {
DATABASE_ENCRYPTION_KEY = databaseEncryptionKey;
}

}
Loading