Skip to content

Commit

Permalink
Relaxed isolation of CryptoShreddingKeyService::getExistingSecretKey()
Browse files Browse the repository at this point in the history
to allow reading of commited keys within an ongoing transaction
  • Loading branch information
sluehr committed Apr 8, 2022
1 parent fad6ba6 commit 6a5d842
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
import engineering.everest.axon.cryptoshredding.persistence.SecretKeyRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Optional;

import static org.springframework.transaction.annotation.Isolation.READ_UNCOMMITTED;

/**
* Service level cryptographic key management.
*/
Expand Down Expand Up @@ -47,6 +50,7 @@ public Optional<SecretKey> getOrCreateSecretKeyUnlessDeleted(TypeDifferentiatedS
* @param keyId that uniquely identifies the key
* @return an optional secret key
*/
@Transactional(isolation = READ_UNCOMMITTED)
public Optional<SecretKey> getExistingSecretKey(TypeDifferentiatedSecretKeyId keyId) {
var optionalPersistableSecretKey = secretKeyRepository.findById(keyId);
if (optionalPersistableSecretKey.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package engineering.everest.axon.cryptoshredding;

import engineering.everest.axon.cryptoshredding.annotations.EncryptionKeyIdentifier;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;

/**
* Secret key identifier that includes a {@code keyType} parameter. This is used to differentiate between identifiers when key uniqueness
Expand All @@ -15,10 +13,47 @@
* @see EncryptionKeyIdentifier
*/
@Embeddable
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TypeDifferentiatedSecretKeyId implements Serializable {
private String keyId;
private String keyType;

public TypeDifferentiatedSecretKeyId() {}

public TypeDifferentiatedSecretKeyId(String keyId, String keyType) {
this.keyId = keyId;
this.keyType = keyType;
}

public String getKeyId() {
return keyId;
}

public void setKeyId(String keyId) {
this.keyId = keyId;
}

public String getKeyType() {
return keyType;
}

public void setKeyType(String keyType) {
this.keyType = keyType;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TypeDifferentiatedSecretKeyId that = (TypeDifferentiatedSecretKeyId) o;
return Objects.equals(keyId, that.keyId) && Objects.equals(keyType, that.keyType);
}

@Override
public int hashCode() {
return Objects.hash(keyId, keyType);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,67 @@
package engineering.everest.axon.cryptoshredding.persistence;

import engineering.everest.axon.cryptoshredding.TypeDifferentiatedSecretKeyId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.Hibernate;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Lob;
import java.util.Objects;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity(name = "cryptoshreddingkeys")
public class PersistableSecretKey {
@EmbeddedId
private TypeDifferentiatedSecretKeyId id;
@Lob
private byte[] key;
private String algorithm;

public PersistableSecretKey() {}

public PersistableSecretKey(TypeDifferentiatedSecretKeyId id, byte[] key, String algorithm) {
this.id = id;
this.key = key;
this.algorithm = algorithm;
}

public TypeDifferentiatedSecretKeyId getId() {
return id;
}

public void setId(TypeDifferentiatedSecretKeyId id) {
this.id = id;
}

public byte[] getKey() {
return key;
}

public void setKey(byte[] key) {
this.key = key;
}

public String getAlgorithm() {
return algorithm;
}

public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
return false;
}
PersistableSecretKey that = (PersistableSecretKey) o;
return id != null && Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Optional;

import static java.util.UUID.randomUUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -26,61 +27,68 @@
@ContextConfiguration(classes = TestsJpaConfig.class)
class CryptoShreddingKeyServiceIntegrationTest {

private static final TypeDifferentiatedSecretKeyId SECRET_KEY_ID = new TypeDifferentiatedSecretKeyId("secret-key-id", "");

@Autowired
private SecretKeyRepository secretKeyRepository;
@Autowired
private CryptoShreddingKeyService cryptoShreddingKeyService;

@Test
void getOrCreateSecretKeyUnlessDeleted_WillCreateSecretKeyOnFirstGet() {
var secretKey = cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID);
var secretKey = cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(generateKeyId());

assertEquals("AES", secretKey.orElseThrow().getAlgorithm());
}

@Test
void getOrCreateSecretKeyUnlessDeleted_WillReturnEmptyOptional_WhenKeyHasBeenDeleted() {
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID);
cryptoShreddingKeyService.deleteSecretKey(SECRET_KEY_ID);
var keyId = generateKeyId();
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId);
cryptoShreddingKeyService.deleteSecretKey(keyId);

assertTrue(cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID).isEmpty());
assertTrue(cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId).isEmpty());
}

@Test
void getExistingSecretKey_WillRetrievePreviouslyCreatedKey() {
var secretKey1 = cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID);
var secretKey2 = cryptoShreddingKeyService.getExistingSecretKey(SECRET_KEY_ID);
var keyId = generateKeyId();
var secretKey1 = cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId);
var secretKey2 = cryptoShreddingKeyService.getExistingSecretKey(keyId);

assertEquals(secretKey1.orElseThrow(), secretKey2.orElseThrow());
}

@Test
void getExistingSecretKey_WillFailWhenKeyNotCreated() {
assertThrows(MissingEncryptionKeyRecordException.class, () -> cryptoShreddingKeyService.getExistingSecretKey(SECRET_KEY_ID));

assertThrows(MissingEncryptionKeyRecordException.class, () -> cryptoShreddingKeyService.getExistingSecretKey(generateKeyId()));
}

@Test
void getExistingSecretKey_WillReturnEmptyOptional_WhenKeyPreviouslyCreatedAndDeleted() {
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID);
cryptoShreddingKeyService.deleteSecretKey(SECRET_KEY_ID);
var keyId = generateKeyId();
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId);
cryptoShreddingKeyService.deleteSecretKey(keyId);

assertEquals(Optional.empty(), cryptoShreddingKeyService.getExistingSecretKey(SECRET_KEY_ID));
assertEquals(Optional.empty(), cryptoShreddingKeyService.getExistingSecretKey(keyId));
}

@Test
void keysAreIdempotentlyDeleted() {
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID);
cryptoShreddingKeyService.deleteSecretKey(SECRET_KEY_ID);
cryptoShreddingKeyService.deleteSecretKey(SECRET_KEY_ID);
var keyId = generateKeyId();
cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId);
cryptoShreddingKeyService.deleteSecretKey(keyId);
cryptoShreddingKeyService.deleteSecretKey(keyId);

assertTrue(cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(SECRET_KEY_ID).isEmpty());
assertTrue(cryptoShreddingKeyService.getOrCreateSecretKeyUnlessDeleted(keyId).isEmpty());
}

@Test
void deletingANonExistentKey_WillFail() {
assertThrows(MissingEncryptionKeyRecordException.class, () -> cryptoShreddingKeyService.deleteSecretKey(
new TypeDifferentiatedSecretKeyId("does not exist", "")));
}

private TypeDifferentiatedSecretKeyId generateKeyId() {
return new TypeDifferentiatedSecretKeyId(randomUUID().toString(), "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import engineering.everest.axon.cryptoshredding.CryptoShreddingKeyService;
import engineering.everest.axon.cryptoshredding.CryptoShreddingSerializer;
import engineering.everest.axon.cryptoshredding.testevents.EventWithDifferentiatedKeyType;
import engineering.everest.axon.cryptoshredding.testevents.EventWithEncryptedFields;
import engineering.everest.axon.cryptoshredding.testevents.EventWithMissingEncryptionKeyIdentifierAnnotation;
import engineering.everest.axon.cryptoshredding.testevents.EventWithUnsupportedEncryptionKeyIdentifierType;
import engineering.everest.axon.cryptoshredding.testevents.EventWithoutEncryptedFields;
import engineering.everest.axon.cryptoshredding.TypeDifferentiatedSecretKeyId;
import engineering.everest.axon.cryptoshredding.exceptions.EncryptionKeyDeletedException;
import engineering.everest.axon.cryptoshredding.exceptions.MissingEncryptionKeyIdentifierAnnotation;
import engineering.everest.axon.cryptoshredding.exceptions.MissingSerializedEncryptionKeyIdentifierFieldException;
import engineering.everest.axon.cryptoshredding.exceptions.UnsupportedEncryptionKeyIdentifierTypeException;
import engineering.everest.axon.cryptoshredding.persistence.SecretKeyRepository;
import engineering.everest.axon.cryptoshredding.testevents.EventWithDifferentiatedKeyType;
import engineering.everest.axon.cryptoshredding.testevents.EventWithEncryptedFields;
import engineering.everest.axon.cryptoshredding.testevents.EventWithMissingEncryptionKeyIdentifierAnnotation;
import engineering.everest.axon.cryptoshredding.testevents.EventWithUnsupportedEncryptionKeyIdentifierType;
import engineering.everest.axon.cryptoshredding.testevents.EventWithoutEncryptedFields;
import org.axonframework.serialization.Converter;
import org.axonframework.serialization.Serializer;
import org.axonframework.serialization.SimpleSerializedObject;
Expand Down Expand Up @@ -64,10 +64,10 @@ class CryptoShreddingSerializerTest {

@BeforeEach
void setUp() {
cryptoShreddingSerializerWithMock =
new CryptoShreddingSerializer(mockWrappedSerializer, cryptoShreddingKeyService, encrypterFactory, new ObjectMapper());
jsonCryptoShreddingSerializer = new CryptoShreddingSerializer(JacksonSerializer.defaultSerializer(), cryptoShreddingKeyService,
encrypterFactory, new ObjectMapper());
cryptoShreddingSerializerWithMock = new CryptoShreddingSerializer(
mockWrappedSerializer, cryptoShreddingKeyService, encrypterFactory, new ObjectMapper());
jsonCryptoShreddingSerializer = new CryptoShreddingSerializer(
JacksonSerializer.defaultSerializer(), cryptoShreddingKeyService, encrypterFactory, new ObjectMapper());
var secureRandom = new SecureRandom();
defaultAesEncrypter = new DefaultAesEncrypter(secureRandom);
defaultAesDecrypter = new DefaultAesDecrypter(secureRandom);
Expand Down Expand Up @@ -99,7 +99,6 @@ void serialize_WillFailWhenEncryptionKeyHasBeenDeleted() {

assertThrows(EncryptionKeyDeletedException.class,
() -> jsonCryptoShreddingSerializer.serialize(EventWithEncryptedFields.createTestInstance(), byte[].class));

}

@Test
Expand Down Expand Up @@ -202,9 +201,8 @@ void deserialize_WillFail_WhenEncryptionKeyIdentifierFieldHasBeenDeletedOrRename
new SimpleSerializedObject<>(mangledPayload, byte[].class,
new SimpleSerializedType(EventWithEncryptedFields.class.getCanonicalName(), null));

assertThrows(MissingSerializedEncryptionKeyIdentifierFieldException.class, () -> {
EventWithEncryptedFields deserialized = jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent);
});
assertThrows(MissingSerializedEncryptionKeyIdentifierFieldException.class,
() -> jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent));
}

@Test
Expand All @@ -219,9 +217,8 @@ void deserialize_WillFail_WhenEncryptionKeyIdentifierValueHasBeenDeletedOrRename
new SimpleSerializedObject<>(mangledPayload, byte[].class,
new SimpleSerializedType(EventWithEncryptedFields.class.getCanonicalName(), null));

assertThrows(MissingSerializedEncryptionKeyIdentifierFieldException.class, () -> {
EventWithEncryptedFields deserialized = jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent);
});
assertThrows(MissingSerializedEncryptionKeyIdentifierFieldException.class,
() -> jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent));
}

@Test
Expand All @@ -235,10 +232,8 @@ void deserialize_WillFail_WhenEncryptionKeyIdentifierAnnotationIsMissing() {
new SimpleSerializedObject<>(serializedAndEncryptedEvent.getData(), byte[].class,
new SimpleSerializedType(EventWithMissingEncryptionKeyIdentifierAnnotation.class.getCanonicalName(), REVISION_NUMBER));

assertThrows(MissingEncryptionKeyIdentifierAnnotation.class, () -> {
EventWithMissingEncryptionKeyIdentifierAnnotation deserialized =
jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent);
});
assertThrows(MissingEncryptionKeyIdentifierAnnotation.class,
() -> jsonCryptoShreddingSerializer.deserialize(typeInformationAugmentedEncryptedEvent));
}

@Test
Expand Down

0 comments on commit 6a5d842

Please sign in to comment.