Skip to content
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
2 changes: 0 additions & 2 deletions .github/kts/build.main.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
@file:DependsOn("actions:checkout:v4")
@file:DependsOn("actions:cache:v4")
@file:DependsOn("actions:setup-java:v4")
@file:DependsOn("codecov:codecov-action:v5")


import io.github.typesafegithub.workflows.actions.actions.Cache
import io.github.typesafegithub.workflows.actions.actions.Checkout
import io.github.typesafegithub.workflows.actions.actions.SetupJava
import io.github.typesafegithub.workflows.actions.codecov.CodecovAction
import io.github.typesafegithub.workflows.domain.RunnerType
import io.github.typesafegithub.workflows.domain.triggers.PullRequest
import io.github.typesafegithub.workflows.domain.triggers.Push
Expand Down
40 changes: 0 additions & 40 deletions codecov.yml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2020-2023 PRISMA European Capacity Platform GmbH
* Copyright © 2020-2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,8 +15,6 @@
*/
package eu.prismacapacity.cryptoshred.cloud.aws;

import java.util.Optional;

import eu.prismacapacity.cryptoshred.core.CryptoAlgorithm;
import eu.prismacapacity.cryptoshred.core.CryptoEngine;
import eu.prismacapacity.cryptoshred.core.CryptoSubjectId;
Expand All @@ -25,6 +23,7 @@
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeyRepository;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeySize;
import eu.prismacapacity.cryptoshred.core.metrics.CryptoMetrics;
import java.util.Optional;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;
Expand All @@ -48,7 +47,7 @@ public class DynamoDBCryptoKeyRepository implements CryptoKeyRepository {
@NonNull private final String tableName;

@Override
public Optional<CryptoKey> findKeyFor(
public @NonNull Optional<CryptoKey> findKeyFor(
@NonNull CryptoSubjectId subjectId,
@NonNull CryptoAlgorithm algorithm,
@NonNull CryptoKeySize size) {
Expand All @@ -66,7 +65,7 @@ public Optional<CryptoKey> findKeyFor(
}

@Override
public CryptoKey getOrCreateKeyFor(
public @NonNull CryptoKey getOrCreateKeyFor(
@NonNull CryptoSubjectId subjectId,
@NonNull CryptoAlgorithm algorithm,
@NonNull CryptoKeySize size)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2020-2023 PRISMA European Capacity Platform GmbH
* Copyright © 2020-2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,23 +17,21 @@

import static org.junit.Assert.*;

import eu.prismacapacity.cryptoshred.cloud.aws.utils.TestIntegration;
import eu.prismacapacity.cryptoshred.core.*;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKey;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeySize;
import eu.prismacapacity.cryptoshred.core.metrics.CryptoMetrics;
import java.net.URI;
import java.util.HashMap;
import java.util.UUID;

import lombok.NonNull;
import lombok.val;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import eu.prismacapacity.cryptoshred.cloud.aws.utils.TestIntegration;
import eu.prismacapacity.cryptoshred.core.*;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKey;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeySize;
import eu.prismacapacity.cryptoshred.core.metrics.CryptoMetrics;
import lombok.NonNull;
import lombok.val;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
Expand All @@ -43,7 +41,7 @@
class DynamoDBCryptoKeyRepositoryIntegrationTest {
private static String TABLE_NAME = "foo";

CryptoEngine engine = new JDKCryptoEngine(CryptoInitializationVector.of("mysecret"));
CryptoEngine engine = new JDKCryptoEngine("mysecret", false);
CryptoMetrics metrics = new CryptoMetrics.NOP();

@Container
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright © 2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.prismacapacity.cryptoshred.core;

import java.security.SecureRandom;
import java.util.*;
import javax.crypto.spec.IvParameterSpec;
import lombok.NonNull;

public abstract class AbstractCryptoEngine implements CryptoEngine {
protected final CryptoInitializationVector configuredInitVector;

protected final boolean useRandomInitVector;

protected final Map<CryptoAlgorithm, String> exactCipherNames = createExactCipherMapping();

protected static final SecureRandom RANDOM = new SecureRandom();

protected AbstractCryptoEngine(String configuredInitVectorOrNull, boolean useRandomInitVector) {
if (!useRandomInitVector && null == configuredInitVectorOrNull) {
throw new IllegalArgumentException(
"No init vector configured, and useRandomInitVector is false.");
}

this.useRandomInitVector = useRandomInitVector;

if (configuredInitVectorOrNull == null) {
this.configuredInitVector = null;
} else this.configuredInitVector = CryptoInitializationVector.of(configuredInitVectorOrNull);
}

private static Map<CryptoAlgorithm, String> createExactCipherMapping() {
// initialize with known algorithms
HashMap<CryptoAlgorithm, String> map = new HashMap<>();
map.put(CryptoAlgorithm.AES_CBC, "AES/CBC/PKCS5PADDING");
return Collections.unmodifiableMap(map);
}

@NonNull
protected final IvParameterSpec resolveInitVectorForDecryption(
IvParameterSpec initializationVectorProvidedOrNull) {

if (initializationVectorProvidedOrNull != null) {
return initializationVectorProvidedOrNull;
} else {
// no IV stored with the container, so we use the configured one
if (configuredInitVector == null)
throw new IllegalStateException(
"No init vector configured, and none stored with the container.");
else return configuredInitVector.getIvParameterSpec();
}
}

public final @NonNull IvParameterSpec getInitVectorForEncryption() {

if (useRandomInitVector) {
byte[] iv = new byte[16];
RANDOM.nextBytes(iv);
return new IvParameterSpec(iv);
} else
// guaranteed by constructor check
return configuredInitVector.getIvParameterSpec();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2020 PRISMA European Capacity Platform GmbH
* Copyright © 2020-2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,7 @@
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeySize;
import eu.prismacapacity.cryptoshred.core.metrics.CryptoMetrics;
import java.util.Optional;
import javax.crypto.spec.IvParameterSpec;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
Expand Down Expand Up @@ -52,6 +53,7 @@ private CryptoContainer(
CryptoKeySize size,
CryptoSubjectId id,
byte[] encrypted,
byte[] iv,
CryptoEngine engine,
CryptoKeyRepository keyRepo,
CryptoMetrics metrics,
Expand All @@ -66,6 +68,7 @@ private CryptoContainer(
this.metrics = metrics;
this.mapper = om;
this.encryptedBytes = encrypted;
this.initializationVector = iv == null ? null : new IvParameterSpec(iv);
}

// set only on deserialization
Expand All @@ -80,12 +83,13 @@ static <T> CryptoContainer<T> fromDeserialization(
CryptoKeySize keySize,
CryptoSubjectId subjectId,
byte[] encrypted,
byte[] iv,
CryptoEngine engine,
CryptoKeyRepository keyRepo,
CryptoMetrics metrics,
ObjectMapper om) {
return new CryptoContainer<T>(
targetType, algorithm, keySize, subjectId, encrypted, engine, keyRepo, metrics, om);
targetType, algorithm, keySize, subjectId, encrypted, iv, engine, keyRepo, metrics, om);
}

@Getter private Class<?> type;
Expand All @@ -100,6 +104,14 @@ static <T> CryptoContainer<T> fromDeserialization(
@Getter(value = AccessLevel.PACKAGE)
private byte[] encryptedBytes;

/**
* Will be set when encrypting.
*
* <p>For backward-compatibility - if it does not exist, the engine will use the default iv.
*/
@Getter(value = AccessLevel.PACKAGE)
private IvParameterSpec initializationVector = null;

// set after decryption or before encryption for short circuit retrieval
private transient Optional<T> cachedValue;

Expand All @@ -119,7 +131,7 @@ private T decrypt() {
if (key.isPresent()) {

try {
byte[] decrypted = engine.decrypt(getAlgo(), key.get(), bytes);
byte[] decrypted = engine.decrypt(getAlgo(), key.get(), bytes, initializationVector);
T t = mapper.readerFor(getType()).readValue(decrypted);
if (metrics != null) metrics.notifyDecryptionSuccess();
return t;
Expand All @@ -139,9 +151,11 @@ private T decrypt() {

@SneakyThrows
protected void encrypt(CryptoKeyRepository keyRepository, CryptoEngine engine, ObjectMapper om) {

this.initializationVector = engine.getInitVectorForEncryption();

CryptoKey key = keyRepository.getOrCreateKeyFor(subjectId, algo, size);
byte[] bytes;
bytes = om.writeValueAsBytes(value());
this.encryptedBytes = engine.encrypt(bytes, algo, key);
byte[] bytes = om.writeValueAsBytes(value());
this.encryptedBytes = engine.encrypt(bytes, algo, key, this.initializationVector);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2020 PRISMA European Capacity Platform GmbH
* Copyright © 2020-2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@

import eu.prismacapacity.cryptoshred.core.keys.CryptoKey;
import eu.prismacapacity.cryptoshred.core.keys.CryptoKeySize;
import javax.crypto.spec.IvParameterSpec;
import lombok.NonNull;

/**
Expand All @@ -26,14 +27,20 @@
*/
public interface CryptoEngine {

@NonNull
byte[] decrypt(
@NonNull CryptoAlgorithm algo, @NonNull CryptoKey cryptoKey, @NonNull byte[] bytes);
@NonNull CryptoAlgorithm algo,
@NonNull CryptoKey cryptoKey,
byte @NonNull [] bytes,
IvParameterSpec initializationVectorOrNull);

@NonNull
byte[] encrypt(
@NonNull byte[] unencypted, @NonNull CryptoAlgorithm algorithm, @NonNull CryptoKey key);
byte @NonNull [] unencypted,
@NonNull CryptoAlgorithm algorithm,
@NonNull CryptoKey key,
@NonNull IvParameterSpec initializationVector);

@NonNull
CryptoKey generateKey(@NonNull CryptoAlgorithm algo, @NonNull CryptoKeySize size);

IvParameterSpec getInitVectorForEncryption();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2020 PRISMA European Capacity Platform GmbH
* Copyright © 2020-2026 PRISMA European Capacity Platform GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,20 +17,30 @@

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import lombok.NonNull;
import lombok.Value;
import javax.crypto.spec.IvParameterSpec;
import lombok.*;

@Value(staticConstructor = "of")
public class CryptoInitializationVector {
@NonNull String initVector;
@RequiredArgsConstructor
@SuppressWarnings("java:S3329")
public final class CryptoInitializationVector {

public byte[] getBytes() {
@Getter private final byte[] bytes;

public static CryptoInitializationVector of(@NonNull String initVector) {
return new CryptoInitializationVector(toBytes(initVector));
}

static byte[] toBytes(@NonNull String initVector) {
// make sure, we have 16 bytes there
StringBuffer sb = new StringBuffer(initVector);
StringBuilder sb = new StringBuilder(initVector);
while (sb.length() < 16) sb.append(initVector);

byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
// take the first 16 bytes
return Arrays.copyOf(bytes, 16);
}

public IvParameterSpec getIvParameterSpec() {
return new IvParameterSpec(bytes);
}
}
Loading