, M, DM extends M,
- P extends BasePropertiesModel, BLI extends BaseBackupListItem, BL extends List, B extends BaseBackupModel,
- BC extends BackupConverter, MC extends RecoveryAwareConverter,
+ P extends BasePropertiesModel, BLI extends BaseBackupListItem, BL extends BackupListContainer,
+ B extends BaseBackupModel, BC extends BackupConverter, MC extends RecoveryAwareConverter,
S extends BaseVaultFake> extends BaseEntityReadController {
private final MC modelConverter;
@@ -62,7 +63,7 @@ protected M restoreEntity(final B backupModel) {
final String id = getSingleEntityName(backupModel);
final K entityId = entityId(baseUri, id);
assertNameDoesNotExistYet(vault, entityId);
- backupModel.getValue().forEach(entityVersion -> {
+ backupModel.getValue().getVersions().forEach(entityVersion -> {
final V versionedEntityId = versionedEntityId(baseUri, id, entityVersion.getVersion());
restoreVersion(vault, versionedEntityId, entityVersion);
});
@@ -98,11 +99,29 @@ protected B backupEntity(final K entityId) {
protected abstract BL getBackupList();
+ protected String getSingleEntityName(final B backupModel) {
+ final List entityNames = backupModel.getValue().getVersions().stream()
+ .map(BLI::getId)
+ .distinct()
+ .collect(Collectors.toUnmodifiableList());
+ Assert.isTrue(entityNames.size() == 1, "All backup entities must belong to the same entity.");
+ return entityNames.get(0);
+ }
+
+ protected URI getSingleBaseUri(final B backupModel) {
+ final List uris = backupModel.getValue().getVersions().stream()
+ .map(BLI::getVaultBaseUri)
+ .distinct()
+ .collect(Collectors.toUnmodifiableList());
+ Assert.isTrue(uris.size() == 1, "All backup entities must be from the same vault.");
+ return uris.get(0);
+ }
+
private B wrapBackup(final List list) {
final BL listModel = Optional.ofNullable(list)
.map(l -> {
final BL backupList = getBackupList();
- backupList.addAll(l);
+ backupList.setVersions(l);
return backupList;
})
.orElse(null);
@@ -118,22 +137,4 @@ private void assertNameDoesNotExistYet(final S vault, final K entityId) {
"Vault already contains deleted entity with name: " + entityId.id());
}
- private String getSingleEntityName(final B backupModel) {
- final List entityNames = backupModel.getValue().stream()
- .map(BLI::getId)
- .distinct()
- .collect(Collectors.toUnmodifiableList());
- Assert.isTrue(entityNames.size() == 1, "All backup entities must belong to the same entity.");
- return entityNames.get(0);
- }
-
- private URI getSingleBaseUri(final B backupModel) {
- final List uris = backupModel.getValue().stream()
- .map(BLI::getVaultBaseUri)
- .distinct()
- .collect(Collectors.toUnmodifiableList());
- Assert.isTrue(uris.size() == 1, "All backup entities must be from the same vault.");
- return uris.get(0);
- }
-
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyBackupRestoreController.java
index 38bf9aeb..3304a9fa 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyBackupRestoreController.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonKeyBackupRestoreController.java
@@ -1,8 +1,14 @@
package com.github.nagyesta.lowkeyvault.controller.common;
-import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72BackupConverter;
+import com.github.nagyesta.lowkeyvault.mapper.common.BackupConverter;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter;
-import com.github.nagyesta.lowkeyvault.model.v7_2.key.*;
+import com.github.nagyesta.lowkeyvault.model.v7_2.common.BaseBackupListItem;
+import com.github.nagyesta.lowkeyvault.model.v7_2.common.BaseBackupModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.BackupListContainer;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.DeletedKeyVaultKeyModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyPropertiesModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest;
import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity;
import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
@@ -20,19 +26,30 @@
import java.util.Collections;
import java.util.Objects;
+/**
+ * Common logic of backup and restore controllers across the different API versions.
+ *
+ * @param The type of the list item representing one entity version in the backup model.
+ * @param The wrapper type of the list in the backup model.
+ * @param The type of the backup model.
+ * @param The converter, converting entities to list items of the backup models.
+ */
@Slf4j
-public abstract class CommonKeyBackupRestoreController extends BaseBackupRestoreController {
+public abstract class CommonKeyBackupRestoreController,
+ BL extends BackupListContainer, B extends BaseBackupModel,
+ BC extends BackupConverter>
+ extends BaseBackupRestoreController {
protected CommonKeyBackupRestoreController(
@NonNull final KeyEntityToV72ModelConverter modelConverter,
- @NonNull final KeyEntityToV72BackupConverter backupConverter,
+ @NonNull final BC backupConverter,
@NonNull final VaultService vaultService) {
super(modelConverter, backupConverter, vaultService, VaultFake::keyVaultFake);
}
- public ResponseEntity backup(
+ public ResponseEntity backup(
@Valid @Pattern(regexp = NAME_PATTERN) final String keyName,
final URI baseUri) {
log.info("Received request to {} backup key: {} using API version: {}",
@@ -42,32 +59,22 @@ public ResponseEntity backup(
public ResponseEntity restore(
final URI baseUri,
- @Valid final KeyBackupModel keyBackupModel) {
+ @Valid final B keyBackupModel) {
log.info("Received request to {} restore key: {} using API version: {}",
- baseUri.toString(), keyBackupModel.getValue().get(0).getId(), apiVersion());
+ baseUri.toString(), keyBackupModel.getValue().getVersions().get(0).getId(), apiVersion());
return ResponseEntity.ok(restoreEntity(keyBackupModel));
}
@Override
protected void restoreVersion(@NonNull final KeyVaultFake vault,
@NonNull final VersionedKeyEntityId versionedEntityId,
- @NonNull final KeyBackupListItem entityVersion) {
- vault.importKeyVersion(versionedEntityId, entityVersion.getKeyMaterial());
+ @NonNull final BLI entityVersion) {
+ vault.importKeyVersion(versionedEntityId, getKeyMaterial(entityVersion));
final KeyVaultKeyEntity, ?> entity = vault.getEntities().getEntity(versionedEntityId, KeyVaultKeyEntity.class);
- entity.setOperations(Objects.requireNonNullElse(entityVersion.getKeyMaterial().getKeyOps(), Collections.emptyList()));
+ entity.setOperations(Objects.requireNonNullElse(getKeyMaterial(entityVersion).getKeyOps(), Collections.emptyList()));
updateCommonFields(entityVersion, entity);
}
- @Override
- protected KeyBackupList getBackupList() {
- return new KeyBackupList();
- }
-
- @Override
- protected KeyBackupModel getBackupModel() {
- return new KeyBackupModel();
- }
-
@Override
protected VersionedKeyEntityId versionedEntityId(final URI baseUri, final String name, final String version) {
return new VersionedKeyEntityId(baseUri, name, version);
@@ -77,4 +84,6 @@ protected VersionedKeyEntityId versionedEntityId(final URI baseUri, final String
protected KeyEntityId entityId(final URI baseUri, final String name) {
return new KeyEntityId(baseUri, name);
}
+
+ protected abstract JsonWebKeyImportRequest getKeyMaterial(BLI entityVersion);
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretBackupRestoreController.java
index ddc3298a..4a428566 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretBackupRestoreController.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/common/CommonSecretBackupRestoreController.java
@@ -41,7 +41,7 @@ public ResponseEntity restore(
final URI baseUri,
@Valid final SecretBackupModel secretBackupModel) {
log.info("Received request to {} restore secret: {} using API version: {}",
- baseUri.toString(), secretBackupModel.getValue().get(0).getId(), apiVersion());
+ baseUri.toString(), secretBackupModel.getValue().getVersions().get(0).getId(), apiVersion());
return ResponseEntity.ok(restoreEntity(secretBackupModel));
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreController.java
index 48318451..7d3f68f3 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreController.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreController.java
@@ -4,8 +4,11 @@
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72BackupConverter;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter;
import com.github.nagyesta.lowkeyvault.model.common.ApiConstants;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupList;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupListItem;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupModel;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest;
import com.github.nagyesta.lowkeyvault.service.vault.VaultService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,7 +29,8 @@
@RestController
@Validated
@Component("KeyBackupRestoreControllerV72")
-public class KeyBackupRestoreController extends CommonKeyBackupRestoreController {
+public class KeyBackupRestoreController
+ extends CommonKeyBackupRestoreController {
@Autowired
protected KeyBackupRestoreController(final KeyEntityToV72ModelConverter modelConverter,
@@ -54,6 +58,21 @@ public ResponseEntity restore(@RequestAttribute(name = ApiCons
return super.restore(baseUri, keyBackupModel);
}
+ @Override
+ protected JsonWebKeyImportRequest getKeyMaterial(final KeyBackupListItem entityVersion) {
+ return entityVersion.getKeyMaterial();
+ }
+
+ @Override
+ protected KeyBackupList getBackupList() {
+ return new KeyBackupList();
+ }
+
+ @Override
+ protected KeyBackupModel getBackupModel() {
+ return new KeyBackupModel();
+ }
+
@Override
protected String apiVersion() {
return V_7_2;
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreController.java
index 87e65933..eee44845 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreController.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreController.java
@@ -3,13 +3,23 @@
import com.github.nagyesta.lowkeyvault.controller.common.CommonKeyBackupRestoreController;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72BackupConverter;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter;
+import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyToV73ModelConverter;
+import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyV73ModelToEntityConverter;
import com.github.nagyesta.lowkeyvault.model.common.ApiConstants;
-import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupListItem;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupList;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupModel;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyRotationPolicyModel;
+import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
import com.github.nagyesta.lowkeyvault.service.vault.VaultService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
+import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -17,6 +27,7 @@
import javax.validation.Valid;
import javax.validation.constraints.Pattern;
import java.net.URI;
+import java.util.Optional;
import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.API_VERSION_7_3;
import static com.github.nagyesta.lowkeyvault.model.common.ApiConstants.V_7_3;
@@ -26,13 +37,21 @@
@RestController
@Validated
@Component("KeyBackupRestoreControllerV73")
-public class KeyBackupRestoreController extends CommonKeyBackupRestoreController {
+public class KeyBackupRestoreController
+ extends CommonKeyBackupRestoreController {
+
+ private final KeyRotationPolicyToV73ModelConverter keyRotationPolicyToV73ModelConverter;
+ private final KeyRotationPolicyV73ModelToEntityConverter rotationV73ModelToEntityConverter;
@Autowired
- protected KeyBackupRestoreController(final KeyEntityToV72ModelConverter modelConverter,
- final KeyEntityToV72BackupConverter backupConverter,
- final VaultService vaultService) {
+ protected KeyBackupRestoreController(@NonNull final KeyEntityToV72ModelConverter modelConverter,
+ @NonNull final KeyEntityToV72BackupConverter backupConverter,
+ @NonNull final VaultService vaultService,
+ @NonNull final KeyRotationPolicyToV73ModelConverter keyRotationPolicyToV73ModelConverter,
+ @NonNull final KeyRotationPolicyV73ModelToEntityConverter rotationV73ModelToEntityConverter) {
super(modelConverter, backupConverter, vaultService);
+ this.keyRotationPolicyToV73ModelConverter = keyRotationPolicyToV73ModelConverter;
+ this.rotationV73ModelToEntityConverter = rotationV73ModelToEntityConverter;
}
@Override
@@ -41,7 +60,6 @@ protected KeyBackupRestoreController(final KeyEntityToV72ModelConverter modelCon
produces = APPLICATION_JSON_VALUE)
public ResponseEntity backup(@PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName,
@RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) {
- //handle differences of rotation policy
return super.backup(keyName, baseUri);
}
@@ -55,8 +73,46 @@ public ResponseEntity restore(@RequestAttribute(name = ApiCons
return super.restore(baseUri, keyBackupModel);
}
+ @Override
+ protected KeyBackupModel backupEntity(final KeyEntityId entityId) {
+ final KeyBackupModel keyBackupModel = super.backupEntity(entityId);
+ final KeyBackupList value = keyBackupModel.getValue();
+ final ReadOnlyRotationPolicy rotationPolicy = getVaultByUri(entityId.vault()).rotationPolicy(entityId);
+ value.setKeyRotationPolicy(keyRotationPolicyToV73ModelConverter.convert(rotationPolicy));
+ return keyBackupModel;
+ }
+
+ @Override
+ protected KeyVaultKeyModel restoreEntity(final KeyBackupModel backupModel) {
+ final KeyVaultKeyModel keyVaultKeyModel = super.restoreEntity(backupModel);
+ final URI baseUri = getSingleBaseUri(backupModel);
+ final String entityName = getSingleEntityName(backupModel);
+ final KeyEntityId keyEntityId = entityId(baseUri, entityName);
+ final KeyVaultFake vaultByUri = getVaultByUri(baseUri);
+ final KeyRotationPolicyModel keyRotationPolicy = backupModel.getValue().getKeyRotationPolicy();
+ Optional.ofNullable(keyRotationPolicy)
+ .map(r -> rotationV73ModelToEntityConverter.convert(keyEntityId, r))
+ .ifPresent(vaultByUri::setRotationPolicy);
+ return keyVaultKeyModel;
+ }
+
@Override
protected String apiVersion() {
return V_7_3;
}
+
+ @Override
+ protected JsonWebKeyImportRequest getKeyMaterial(final KeyBackupListItem entityVersion) {
+ return entityVersion.getKeyMaterial();
+ }
+
+ @Override
+ protected KeyBackupList getBackupList() {
+ return new KeyBackupList();
+ }
+
+ @Override
+ protected KeyBackupModel getBackupModel() {
+ return new KeyBackupModel();
+ }
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java
index 8b25189e..1b17dcb6 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyController.java
@@ -11,6 +11,8 @@
import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.CreateKeyRequest;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.ImportKeyRequest;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.UpdateKeyRequest;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity;
+import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId;
import com.github.nagyesta.lowkeyvault.service.vault.VaultService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -142,6 +144,19 @@ public ResponseEntity updateVersion(
return super.updateVersion(keyName, keyVersion, baseUri, request);
}
+ @PostMapping(value = "/keys/{keyName}/rotate",
+ params = API_VERSION_7_3,
+ produces = APPLICATION_JSON_VALUE)
+ public ResponseEntity rotateKey(@PathVariable @Valid @Pattern(regexp = NAME_PATTERN) final String keyName,
+ @RequestAttribute(name = ApiConstants.REQUEST_BASE_URI) final URI baseUri) {
+ log.info("Received request to {} rotate key: {} using API version: {}",
+ baseUri.toString(), keyName, apiVersion());
+
+ final VersionedKeyEntityId rotatedKeyId = getVaultByUri(baseUri).rotateKey(entityId(baseUri, keyName));
+ final ReadOnlyKeyVaultKeyEntity keyVaultKeyEntity = getEntityByNameAndVersion(baseUri, keyName, rotatedKeyId.version());
+ return ResponseEntity.ok(convertDetails(keyVaultKeyEntity));
+ }
+
@Override
@GetMapping(value = "/deletedkeys/{keyName}",
params = API_VERSION_7_3,
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverter.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverter.java
new file mode 100644
index 00000000..594cfdd9
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverter.java
@@ -0,0 +1,54 @@
+package com.github.nagyesta.lowkeyvault.mapper.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.*;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Component
+public class KeyRotationPolicyToV73ModelConverter implements Converter {
+
+ @Override
+ public KeyRotationPolicyModel convert(@Nullable final ReadOnlyRotationPolicy source) {
+ return Optional.ofNullable(source)
+ .map(this::convertNonNull)
+ .orElse(null);
+ }
+
+ private KeyRotationPolicyModel convertNonNull(final ReadOnlyRotationPolicy readOnlyRotationPolicy) {
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(readOnlyRotationPolicy.getId().asRotationPolicyUri());
+ model.setAttributes(convertAttributes(readOnlyRotationPolicy));
+ model.setLifetimeActions(convertLifetimeActions(readOnlyRotationPolicy.getLifetimeActions()));
+ return model;
+ }
+
+ private KeyRotationPolicyAttributes convertAttributes(final ReadOnlyRotationPolicy readOnlyRotationPolicy) {
+ final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes();
+ attributes.setExpiryTime(readOnlyRotationPolicy.getExpiryTime());
+ attributes.setCreatedOn(readOnlyRotationPolicy.getCreatedOn());
+ attributes.setUpdatedOn(readOnlyRotationPolicy.getUpdatedOn());
+ return attributes;
+ }
+
+ private List convertLifetimeActions(final Map lifetimeActions) {
+ return lifetimeActions.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .map(e -> this.convertLifetimeAction(e.getValue()))
+ .collect(Collectors.toList());
+ }
+
+ private KeyLifetimeActionModel convertLifetimeAction(final LifetimeAction lifetimeAction) {
+ return new KeyLifetimeActionModel(
+ new KeyLifetimeActionTypeModel(lifetimeAction.getActionType()),
+ new KeyLifetimeActionTriggerModel(lifetimeAction.getTrigger()));
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverter.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverter.java
new file mode 100644
index 00000000..b77649df
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverter.java
@@ -0,0 +1,75 @@
+package com.github.nagyesta.lowkeyvault.mapper.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.*;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.KeyLifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.RotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyLifetimeActionTrigger;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyRotationPolicy;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Component
+public class KeyRotationPolicyV73ModelToEntityConverter {
+
+ public RotationPolicy convert(final KeyEntityId keyEntityId, @Nullable final KeyRotationPolicyModel source) {
+ return Optional.ofNullable(source)
+ .filter(this::isNotEmpty)
+ .map(s -> convertNonNull(keyEntityId, s))
+ .orElse(null);
+ }
+
+ private boolean isNotEmpty(final KeyRotationPolicyModel rotationPolicyModel) {
+ return hasLifetimeActions(rotationPolicyModel) || hasAttributes(rotationPolicyModel);
+ }
+
+ private boolean hasAttributes(final KeyRotationPolicyModel rotationPolicyModel) {
+ return rotationPolicyModel.getAttributes() != null;
+ }
+
+ private RotationPolicy convertNonNull(final KeyEntityId keyEntityId, final KeyRotationPolicyModel source) {
+ Assert.notNull(source.getAttributes(), "Attributes cannot be null.");
+ Assert.notNull(source.getLifetimeActions(), "LifetimeActions cannot be null.");
+ Assert.notEmpty(source.getLifetimeActions(), "LifetimeActions cannot be empty.");
+ final Map actions = convertLifetimeActions(source.getLifetimeActions());
+ final Period expiryTime = source.getAttributes().getExpiryTime();
+ final RotationPolicy entity = new KeyRotationPolicy(keyEntityId, expiryTime, actions);
+ return convertAttributes(source.getAttributes(), entity);
+ }
+
+ private boolean hasLifetimeActions(final KeyRotationPolicyModel source) {
+ return source.getLifetimeActions() != null && !source.getLifetimeActions().isEmpty();
+ }
+
+ private RotationPolicy convertAttributes(final KeyRotationPolicyAttributes source, final RotationPolicy entity) {
+ entity.setCreatedOn(Optional.ofNullable(source).map(KeyRotationPolicyAttributes::getCreatedOn).orElse(OffsetDateTime.now()));
+ entity.setUpdatedOn(Optional.ofNullable(source).map(KeyRotationPolicyAttributes::getUpdatedOn).orElse(OffsetDateTime.now()));
+ return entity;
+ }
+
+ private Map convertLifetimeActions(final List lifetimeActions) {
+ return lifetimeActions.stream()
+ .map(this::convertLifetimeAction)
+ .collect(Collectors.toMap(LifetimeAction::getActionType, Function.identity()));
+ }
+
+ private LifetimeAction convertLifetimeAction(final KeyLifetimeActionModel source) {
+ final KeyLifetimeActionTriggerModel sourceTrigger = Objects.requireNonNull(source.getTrigger());
+ final KeyLifetimeActionTypeModel action = Objects.requireNonNull(source.getAction());
+ final KeyLifetimeActionTrigger trigger = new KeyLifetimeActionTrigger(
+ sourceTrigger.getTriggerPeriod(), sourceTrigger.getTriggerType());
+ return new KeyLifetimeAction(action.getType(), trigger);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializer.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializer.java
similarity index 61%
rename from lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializer.java
rename to lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializer.java
index d250bafc..767c4fc4 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializer.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializer.java
@@ -3,13 +3,13 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupList;
-public class Base64ZipKeyDeserializer extends AbstractBase64ZipDeserializer {
+public class Base64ZipV72KeyDeserializer extends AbstractBase64ZipDeserializer {
- public Base64ZipKeyDeserializer() {
+ public Base64ZipV72KeyDeserializer() {
this(new Base64Deserializer(), new ObjectMapper());
}
- protected Base64ZipKeyDeserializer(final Base64Deserializer base64Deserializer, final ObjectMapper objectMapper) {
+ protected Base64ZipV72KeyDeserializer(final Base64Deserializer base64Deserializer, final ObjectMapper objectMapper) {
super(base64Deserializer, objectMapper);
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializer.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializer.java
similarity index 54%
rename from lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializer.java
rename to lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializer.java
index 54a07735..62cb2d1b 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializer.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializer.java
@@ -3,13 +3,13 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupList;
-public class Base64ZipKeySerializer extends AbstractBase64ZipSerializer {
+public class Base64ZipV72KeySerializer extends AbstractBase64ZipSerializer {
- public Base64ZipKeySerializer() {
+ public Base64ZipV72KeySerializer() {
this(new Base64Serializer(), new ObjectMapper());
}
- protected Base64ZipKeySerializer(final Base64Serializer base64Serializer, final ObjectMapper objectMapper) {
+ protected Base64ZipV72KeySerializer(final Base64Serializer base64Serializer, final ObjectMapper objectMapper) {
super(base64Serializer, objectMapper);
}
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializer.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializer.java
new file mode 100644
index 00000000..8a3b285d
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializer.java
@@ -0,0 +1,20 @@
+package com.github.nagyesta.lowkeyvault.model.json.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupList;
+
+public class Base64ZipV73KeyDeserializer extends AbstractBase64ZipDeserializer {
+
+ public Base64ZipV73KeyDeserializer() {
+ this(new Base64Deserializer(), new ObjectMapper().findAndRegisterModules());
+ }
+
+ protected Base64ZipV73KeyDeserializer(final Base64Deserializer base64Deserializer, final ObjectMapper objectMapper) {
+ super(base64Deserializer, objectMapper);
+ }
+
+ @Override
+ protected Class getType() {
+ return KeyBackupList.class;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializer.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializer.java
new file mode 100644
index 00000000..d4f349cd
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializer.java
@@ -0,0 +1,15 @@
+package com.github.nagyesta.lowkeyvault.model.json.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupList;
+
+public class Base64ZipV73KeySerializer extends AbstractBase64ZipSerializer {
+
+ public Base64ZipV73KeySerializer() {
+ this(new Base64Serializer(), new ObjectMapper().findAndRegisterModules());
+ }
+
+ protected Base64ZipV73KeySerializer(final Base64Serializer base64Serializer, final ObjectMapper objectMapper) {
+ super(base64Serializer, objectMapper);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/common/BaseBackupModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/common/BaseBackupModel.java
index eaa01a6f..dd752d73 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/common/BaseBackupModel.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/common/BaseBackupModel.java
@@ -2,12 +2,11 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.github.nagyesta.lowkeyvault.model.v7_2.BasePropertiesModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.BackupListContainer;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
-import javax.validation.constraints.Size;
-import java.util.List;
/**
* Base class of backup models.
@@ -18,10 +17,9 @@
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
-public class BaseBackupModel, BL extends List> {
+public class BaseBackupModel, BL extends BackupListContainer> {
@Valid
@NotNull
- @Size(min = 1)
private BL value;
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/BackupListContainer.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/BackupListContainer.java
new file mode 100644
index 00000000..7f2cff0b
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/BackupListContainer.java
@@ -0,0 +1,10 @@
+package com.github.nagyesta.lowkeyvault.model.v7_2.key;
+
+import java.util.List;
+
+public interface BackupListContainer {
+
+ List getVersions();
+
+ void setVersions(List versions);
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupList.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupList.java
index 46a5fd5f..c2f02532 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupList.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupList.java
@@ -1,6 +1,27 @@
package com.github.nagyesta.lowkeyvault.model.v7_2.key;
-import java.util.ArrayList;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
-public class KeyBackupList extends ArrayList {
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+@EqualsAndHashCode
+public class KeyBackupList implements BackupListContainer {
+
+ @Valid
+ @NotNull
+ @Size(min = 1)
+ @JsonProperty("versions")
+ private List versions = List.of();
+
+ public List getVersions() {
+ return versions;
+ }
+
+ public void setVersions(final List versions) {
+ this.versions = List.copyOf(versions);
+ }
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupModel.java
index 93dfda41..793ab6ed 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupModel.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/key/KeyBackupModel.java
@@ -2,8 +2,8 @@
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipKeyDeserializer;
-import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipKeySerializer;
+import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipV72KeyDeserializer;
+import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipV72KeySerializer;
import com.github.nagyesta.lowkeyvault.model.v7_2.common.BaseBackupModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -14,13 +14,13 @@
@ToString(callSuper = true)
public class KeyBackupModel extends BaseBackupModel {
- @JsonSerialize(using = Base64ZipKeySerializer.class)
+ @JsonSerialize(using = Base64ZipV72KeySerializer.class)
@Override
public KeyBackupList getValue() {
return super.getValue();
}
- @JsonDeserialize(using = Base64ZipKeyDeserializer.class)
+ @JsonDeserialize(using = Base64ZipV72KeyDeserializer.class)
@Override
public void setValue(final KeyBackupList value) {
super.setValue(value);
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/secret/SecretBackupList.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/secret/SecretBackupList.java
index 935e1db8..97dbe69a 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/secret/SecretBackupList.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_2/secret/SecretBackupList.java
@@ -1,6 +1,28 @@
package com.github.nagyesta.lowkeyvault.model.v7_2.secret;
-import java.util.ArrayList;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.BackupListContainer;
+import lombok.EqualsAndHashCode;
-public class SecretBackupList extends ArrayList {
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+@EqualsAndHashCode
+public class SecretBackupList implements BackupListContainer {
+
+ @Valid
+ @NotNull
+ @Size(min = 1)
+ @JsonProperty("versions")
+ private List versions = List.of();
+
+ public List getVersions() {
+ return versions;
+ }
+
+ public void setVersions(final List versions) {
+ this.versions = List.copyOf(versions);
+ }
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupList.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupList.java
new file mode 100644
index 00000000..697dce22
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupList.java
@@ -0,0 +1,22 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.Valid;
+
+@EqualsAndHashCode(callSuper = true)
+public class KeyBackupList extends com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupList {
+
+ @Valid
+ @JsonProperty("rotationPolicy")
+ private KeyRotationPolicyModel keyRotationPolicy;
+
+ public KeyRotationPolicyModel getKeyRotationPolicy() {
+ return keyRotationPolicy;
+ }
+
+ public void setKeyRotationPolicy(final KeyRotationPolicyModel keyRotationPolicy) {
+ this.keyRotationPolicy = keyRotationPolicy;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupModel.java
new file mode 100644
index 00000000..5ef627e5
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyBackupModel.java
@@ -0,0 +1,30 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipV73KeyDeserializer;
+import com.github.nagyesta.lowkeyvault.model.json.util.Base64ZipV73KeySerializer;
+import com.github.nagyesta.lowkeyvault.model.v7_2.common.BaseBackupModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupListItem;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyPropertiesModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class KeyBackupModel extends BaseBackupModel {
+
+ @JsonSerialize(using = Base64ZipV73KeySerializer.class)
+ @Override
+ public KeyBackupList getValue() {
+ return super.getValue();
+ }
+
+ @JsonDeserialize(using = Base64ZipV73KeyDeserializer.class)
+ @Override
+ public void setValue(final KeyBackupList value) {
+ super.setValue(value);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionModel.java
new file mode 100644
index 00000000..b1c14908
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionModel.java
@@ -0,0 +1,35 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({"trigger", "action"})
+public class KeyLifetimeActionModel {
+
+ @NotNull
+ @Valid
+ @JsonProperty("trigger")
+ private KeyLifetimeActionTriggerModel trigger;
+ @NotNull
+ @Valid
+ @JsonProperty("action")
+ private KeyLifetimeActionTypeModel action;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public KeyLifetimeActionModel() {
+ }
+
+ public KeyLifetimeActionModel(final KeyLifetimeActionTypeModel action, final KeyLifetimeActionTriggerModel trigger) {
+ this();
+ this.action = action;
+ this.trigger = trigger;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModel.java
new file mode 100644
index 00000000..c6cc21ea
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModel.java
@@ -0,0 +1,60 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+
+import com.fasterxml.jackson.annotation.*;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeActionTrigger;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import lombok.Data;
+import lombok.NonNull;
+import org.springframework.util.Assert;
+
+import java.time.Period;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class KeyLifetimeActionTriggerModel {
+
+ @JsonIgnore
+ private LifetimeActionTriggerType triggerType;
+ @JsonIgnore
+ private Period triggerPeriod;
+
+ @JsonCreator
+ public KeyLifetimeActionTriggerModel(@JsonProperty("timeBeforeExpiry") final Period timeBeforeExpiry,
+ @JsonProperty("timeAfterCreate") final Period timeAfterCreate) {
+ Assert.isTrue(timeBeforeExpiry == null || timeAfterCreate == null,
+ "TimeBeforeExpiry and TimeAfterCreate cannot be populated at the same time.");
+ Assert.isTrue(timeBeforeExpiry != null || timeAfterCreate != null,
+ "TimeBeforeExpiry and TimeAfterCreate cannot be null at the same time.");
+ if (timeAfterCreate != null) {
+ this.triggerType = LifetimeActionTriggerType.TIME_AFTER_CREATE;
+ this.triggerPeriod = timeAfterCreate;
+ } else {
+ this.triggerType = LifetimeActionTriggerType.TIME_BEFORE_EXPIRY;
+ this.triggerPeriod = timeBeforeExpiry;
+ }
+ }
+
+ public KeyLifetimeActionTriggerModel(@NonNull final LifetimeActionTrigger trigger) {
+ this.triggerType = trigger.getTriggerType();
+ this.triggerPeriod = trigger.getTimePeriod();
+ }
+
+ @JsonGetter
+ public Period getTimeBeforeExpiry() {
+ Period period = null;
+ if (triggerType == LifetimeActionTriggerType.TIME_BEFORE_EXPIRY) {
+ period = triggerPeriod;
+ }
+ return period;
+ }
+
+ @JsonGetter
+ public Period getTimeAfterCreate() {
+ Period period = null;
+ if (triggerType == LifetimeActionTriggerType.TIME_AFTER_CREATE) {
+ period = triggerPeriod;
+ }
+ return period;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModel.java
new file mode 100644
index 00000000..3e526220
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModel.java
@@ -0,0 +1,27 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import lombok.Data;
+import lombok.NonNull;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class KeyLifetimeActionTypeModel {
+ @NotNull
+ @JsonProperty("type")
+ private LifetimeActionType type;
+
+ @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+ public KeyLifetimeActionTypeModel() {
+ }
+
+ public KeyLifetimeActionTypeModel(@NonNull final LifetimeActionType type) {
+ this();
+ this.type = type;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyAttributes.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyAttributes.java
new file mode 100644
index 00000000..9169c451
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyAttributes.java
@@ -0,0 +1,34 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.github.nagyesta.lowkeyvault.model.json.util.EpochSecondsDeserializer;
+import com.github.nagyesta.lowkeyvault.model.json.util.EpochSecondsSerializer;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.validator.ExpiryPeriod;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.OffsetDateTime;
+import java.time.Period;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({"expiryTime", "created", "updated"})
+public class KeyRotationPolicyAttributes {
+
+ @JsonProperty("created")
+ @JsonSerialize(using = EpochSecondsSerializer.class)
+ @JsonDeserialize(using = EpochSecondsDeserializer.class)
+ private OffsetDateTime createdOn;
+ @JsonProperty("updated")
+ @JsonSerialize(using = EpochSecondsSerializer.class)
+ @JsonDeserialize(using = EpochSecondsDeserializer.class)
+ private OffsetDateTime updatedOn;
+ @NotNull
+ @ExpiryPeriod
+ @JsonProperty("expiryTime")
+ private Period expiryTime;
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModel.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModel.java
new file mode 100644
index 00000000..85128853
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModel.java
@@ -0,0 +1,29 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.net.URI;
+import java.util.List;
+
+@Data
+@JsonPropertyOrder({"id", "lifetimeActions", "attributes"})
+public class KeyRotationPolicyModel {
+
+ @JsonProperty("id")
+ private URI id;
+ @Valid
+ @NotNull
+ @JsonProperty("attributes")
+ private KeyRotationPolicyAttributes attributes;
+ @Valid
+ @NotNull
+ @Size(min = 1, max = 2)
+ @JsonProperty("lifetimeActions")
+ private List lifetimeActions;
+
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionType.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionType.java
new file mode 100644
index 00000000..1b55ec53
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionType.java
@@ -0,0 +1,33 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key.constants;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.Arrays;
+
+public enum LifetimeActionType {
+ /**
+ * Notification triggers.
+ */
+ NOTIFY("notify"),
+ /**
+ * Automatic rotation trigger.
+ */
+ ROTATE("rotate");
+
+ private final String value;
+
+ LifetimeActionType(final String value) {
+ this.value = value;
+ }
+
+ @JsonCreator
+ public static LifetimeActionType forValue(final String name) {
+ return Arrays.stream(values()).filter(actionType -> actionType.getValue().equals(name)).findFirst().orElse(null);
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriod.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriod.java
new file mode 100644
index 00000000..b68681eb
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriod.java
@@ -0,0 +1,19 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key.validator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(validatedBy = ExpiryPeriodValidator.class)
+public @interface ExpiryPeriod {
+
+ String message() default "Expiry period is invalid.";
+
+ Class>[] groups() default { };
+
+ Class extends Payload>[] payload() default { };
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidator.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidator.java
new file mode 100644
index 00000000..2c2e7ef3
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidator.java
@@ -0,0 +1,25 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key.validator;
+
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.util.PeriodUtil;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.time.Period;
+import java.util.Optional;
+
+public class ExpiryPeriodValidator implements ConstraintValidator {
+
+ @Override
+ public void initialize(final ExpiryPeriod constraintAnnotation) {
+ ConstraintValidator.super.initialize(constraintAnnotation);
+ }
+
+ @Override
+ public boolean isValid(final Period value, final ConstraintValidatorContext context) {
+ return Optional.ofNullable(value)
+ .map(PeriodUtil::asDays)
+ .map(totalDays -> totalDays >= LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS)
+ .orElse(true);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultEntity.java
index 8e0d9968..eaa2d5a9 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultEntity.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultEntity.java
@@ -12,7 +12,7 @@
*
* @param The type of the versioned Id identifying this entity.
*/
-public interface BaseVaultEntity {
+public interface BaseVaultEntity extends TimeAware {
V getId();
@@ -52,8 +52,6 @@ public interface BaseVaultEntity {
boolean canPurge();
- void timeShift(int offsetSeconds);
-
boolean isManaged();
void setCreatedOn(OffsetDateTime createdOn);
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultFake.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultFake.java
index 484d7ff8..4ba1c3c8 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultFake.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/common/BaseVaultFake.java
@@ -12,7 +12,7 @@
* @param The versioned key type.
* @param The entity type.
*/
-public interface BaseVaultFake> {
+public interface BaseVaultFake> extends TimeAware {
ReadOnlyVersionedEntityMultiMap getEntities();
@@ -32,5 +32,4 @@ public interface BaseVaultFake keyCreationInput();
+
default byte[] encrypt(final String clear, final EncryptionAlgorithm encryptionAlgorithm, final byte[] iv) {
Assert.hasText(clear, "Clear text must not be blank.");
return encryptBytes(clear.getBytes(StandardCharsets.UTF_8), encryptionAlgorithm, iv);
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyRotationPolicy.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyRotationPolicy.java
new file mode 100644
index 00000000..cece7a0c
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/ReadOnlyRotationPolicy.java
@@ -0,0 +1,22 @@
+package com.github.nagyesta.lowkeyvault.service.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.Map;
+
+public interface ReadOnlyRotationPolicy {
+
+ KeyEntityId getId();
+
+ OffsetDateTime getCreatedOn();
+
+ OffsetDateTime getUpdatedOn();
+
+ Period getExpiryTime();
+
+ Map getLifetimeActions();
+
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/RotationPolicy.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/RotationPolicy.java
new file mode 100644
index 00000000..f7755bdd
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/RotationPolicy.java
@@ -0,0 +1,21 @@
+package com.github.nagyesta.lowkeyvault.service.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.common.TimeAware;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.Map;
+
+public interface RotationPolicy extends ReadOnlyRotationPolicy, TimeAware {
+
+ void setLifetimeActions(Map lifetimeActions);
+
+ void setCreatedOn(OffsetDateTime createdOn);
+
+ void setUpdatedOn(OffsetDateTime updatedOn);
+
+ void setExpiryTime(Period expiryTime);
+
+ void validate(OffsetDateTime latestKeyVersionExpiry);
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerType.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerType.java
new file mode 100644
index 00000000..ba9aa731
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerType.java
@@ -0,0 +1,75 @@
+package com.github.nagyesta.lowkeyvault.service.key.constants;
+
+import com.github.nagyesta.lowkeyvault.service.key.util.PeriodUtil;
+import lombok.NonNull;
+import org.springframework.util.Assert;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+
+public enum LifetimeActionTriggerType {
+
+ /**
+ * Triggers an action relative to the creation of the key.
+ */
+ TIME_AFTER_CREATE {
+ @Override
+ public boolean shouldTrigger(@NonNull final OffsetDateTime createTime,
+ final OffsetDateTime expiryTime,
+ @NonNull final Period triggerPeriod) {
+ return createTime.plusDays(PeriodUtil.asDays(triggerPeriod)).isBefore(OffsetDateTime.now());
+ }
+
+ @Override
+ public void validate(final OffsetDateTime expiryTime,
+ @NonNull final Period expiryPeriod,
+ @NonNull final Period triggerPeriod) {
+ super.validate(expiryTime, expiryPeriod, triggerPeriod);
+ final Period threshold = expiryPeriod.minusDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ Assert.isTrue(PeriodUtil.asDays(threshold) >= PeriodUtil.asDays(triggerPeriod),
+ "Trigger must be at least " + MINIMUM_THRESHOLD_BEFORE_EXPIRY + " days before expiry.");
+ }
+ },
+
+ /**
+ * Triggers an action relative to the expiry of the key.
+ */
+ TIME_BEFORE_EXPIRY {
+ @Override
+ public boolean shouldTrigger(final OffsetDateTime createTime,
+ @NonNull final OffsetDateTime expiryTime,
+ @NonNull final Period triggerPeriod) {
+ return expiryTime.minusDays(PeriodUtil.asDays(triggerPeriod)).isBefore(OffsetDateTime.now());
+ }
+
+ @Override
+ public void validate(final OffsetDateTime expiryTime,
+ @NonNull final Period expiryPeriod,
+ @NonNull final Period triggerPeriod) {
+ super.validate(expiryTime, expiryPeriod, triggerPeriod);
+ Assert.notNull(expiryTime, "Expiry time is not set, before expiry triggers are not allowed.");
+ Assert.isTrue(PeriodUtil.asDays(triggerPeriod) >= MINIMUM_THRESHOLD_BEFORE_EXPIRY,
+ "Trigger must be at least " + MINIMUM_THRESHOLD_BEFORE_EXPIRY + " days before expiry.");
+ }
+ };
+
+ /**
+ * Minimum number of days we need to leave for a trigger action after creation and before expiry.
+ */
+ public static final int MINIMUM_THRESHOLD_BEFORE_EXPIRY = 7;
+ /**
+ * Minimum number of days needed for expiry periods.
+ */
+ public static final int MINIMUM_EXPIRY_PERIOD_IN_DAYS = 28;
+
+ public abstract boolean shouldTrigger(OffsetDateTime createTime,
+ OffsetDateTime expiryTime,
+ Period triggerPeriod);
+
+ public void validate(final OffsetDateTime expiryTime,
+ final Period expiryPeriod,
+ final Period triggerPeriod) {
+ Assert.isTrue(PeriodUtil.asDays(expiryPeriod) >= MINIMUM_EXPIRY_PERIOD_IN_DAYS,
+ "Expiry period must be at least " + MINIMUM_EXPIRY_PERIOD_IN_DAYS + " days.");
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityId.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityId.java
index 130c4e29..27280c32 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityId.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityId.java
@@ -68,4 +68,8 @@ public URI asRecoveryUri() {
public URI asUri(@NonNull final String query) {
return URI.create(asUri().toString() + query);
}
+
+ public URI asRotationPolicyUri() {
+ return URI.create(vault + "/keys/" + id() + "/rotationpolicy");
+ }
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java
index 927d174e..a30db27b 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntity.java
@@ -41,6 +41,11 @@ public KeyType getKeyType() {
return KeyType.OCT_HSM;
}
+ @Override
+ public KeyCreationInput> keyCreationInput() {
+ return new OctKeyCreationInput(getKeyType(), getKeySize());
+ }
+
@Override
public byte[] getK() {
return getKey().getEncoded();
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/DefaultKeyRotationPolicy.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/DefaultKeyRotationPolicy.java
new file mode 100644
index 00000000..16a0bc82
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/DefaultKeyRotationPolicy.java
@@ -0,0 +1,22 @@
+package com.github.nagyesta.lowkeyvault.service.key.impl;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.KeyLifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+
+import java.time.Period;
+import java.util.Map;
+
+public class DefaultKeyRotationPolicy extends KeyRotationPolicy {
+
+ private static final KeyLifetimeActionTrigger TRIGGER_30_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeActionTrigger(Period.ofDays(30), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+ private static final KeyLifetimeAction NOTIFY_30_DAYS_BEFORE_EXPIRY
+ = new KeyLifetimeAction(LifetimeActionType.NOTIFY, TRIGGER_30_DAYS_BEFORE_EXPIRY);
+ private static final Period PERIOD_1_YEAR = Period.ofYears(1);
+
+ public DefaultKeyRotationPolicy(final KeyEntityId keyEntityId) {
+ super(keyEntityId, PERIOD_1_YEAR, Map.of(LifetimeActionType.NOTIFY, NOTIFY_30_DAYS_BEFORE_EXPIRY));
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java
index 0fb9db64..69051159 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntity.java
@@ -44,6 +44,11 @@ public KeyType getKeyType() {
}
}
+ @Override
+ public KeyCreationInput> keyCreationInput() {
+ return new EcKeyCreationInput(getKeyType(), getKeyCurveName());
+ }
+
@Override
public byte[] getX() {
return ((ECPublicKey) getKey().getPublic()).getW().getAffineX().toByteArray();
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTrigger.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTrigger.java
new file mode 100644
index 00000000..db555e15
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTrigger.java
@@ -0,0 +1,34 @@
+package com.github.nagyesta.lowkeyvault.service.key.impl;
+
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeActionTrigger;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import lombok.NonNull;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+
+public class KeyLifetimeActionTrigger implements LifetimeActionTrigger {
+
+ private final Period timePeriod;
+ private final LifetimeActionTriggerType triggerType;
+
+ public KeyLifetimeActionTrigger(@NonNull final Period timePeriod, @NonNull final LifetimeActionTriggerType triggerType) {
+ this.timePeriod = timePeriod;
+ this.triggerType = triggerType;
+ }
+
+ @Override
+ public Period getTimePeriod() {
+ return timePeriod;
+ }
+
+ @Override
+ public LifetimeActionTriggerType getTriggerType() {
+ return triggerType;
+ }
+
+ @Override
+ public boolean shouldTrigger(final OffsetDateTime created, final OffsetDateTime expiry) {
+ return triggerType.shouldTrigger(created, expiry, timePeriod);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicy.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicy.java
new file mode 100644
index 00000000..db89e2f5
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicy.java
@@ -0,0 +1,104 @@
+package com.github.nagyesta.lowkeyvault.service.key.impl;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.RotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+import lombok.NonNull;
+import org.springframework.util.Assert;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.Map;
+
+public class KeyRotationPolicy implements RotationPolicy {
+
+ private final KeyEntityId keyEntityId;
+ private OffsetDateTime createdOn;
+ private OffsetDateTime updatedOn;
+ private Period expiryTime;
+ private Map lifetimeActions;
+
+ public KeyRotationPolicy(@NonNull final KeyEntityId keyEntityId,
+ @NonNull final Period expiryTime,
+ @NonNull final Map lifetimeActions) {
+ this.keyEntityId = keyEntityId;
+ this.expiryTime = expiryTime;
+ this.lifetimeActions = Map.copyOf(lifetimeActions);
+ this.createdOn = OffsetDateTime.now();
+ this.updatedOn = OffsetDateTime.now();
+ }
+
+ @Override
+ public KeyEntityId getId() {
+ return keyEntityId;
+ }
+
+ @Override
+ public OffsetDateTime getCreatedOn() {
+ return createdOn;
+ }
+
+ @Override
+ public OffsetDateTime getUpdatedOn() {
+ return updatedOn;
+ }
+
+ @Override
+ public Period getExpiryTime() {
+ return expiryTime;
+ }
+
+ @Override
+ public Map getLifetimeActions() {
+ return lifetimeActions;
+ }
+
+ @Override
+ public void validate(final OffsetDateTime latestKeyVersionExpiry) {
+ lifetimeActions.values().forEach(action -> {
+ final Period triggerPeriod = action.getTrigger().getTimePeriod();
+ final LifetimeActionTriggerType triggerType = action.getTrigger().getTriggerType();
+ triggerType.validate(latestKeyVersionExpiry, expiryTime, triggerPeriod);
+ Assert.isTrue(action.getActionType() != LifetimeActionType.NOTIFY
+ || triggerType == LifetimeActionTriggerType.TIME_BEFORE_EXPIRY,
+ "Notify actions cannot be used with time after creation trigger.");
+ });
+ }
+
+ @Override
+ public void setCreatedOn(@NonNull final OffsetDateTime createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ @Override
+ public void setUpdatedOn(@NonNull final OffsetDateTime updatedOn) {
+ this.updatedOn = updatedOn;
+ }
+
+ @Override
+ public void setExpiryTime(@NonNull final Period expiryTime) {
+ this.expiryTime = expiryTime;
+ this.updatedOn = OffsetDateTime.now();
+ }
+
+ @Override
+ public void setLifetimeActions(@NonNull final Map lifetimeActions) {
+ Assert.isTrue(notifyIsNotRemoved(lifetimeActions), "Notify action cannot be removed.");
+ this.lifetimeActions = Map.copyOf(lifetimeActions);
+ this.updatedOn = OffsetDateTime.now();
+ }
+
+ @Override
+ public void timeShift(final int offsetSeconds) {
+ Assert.isTrue(offsetSeconds > 0, "Offset must be positive.");
+ createdOn = createdOn.minusSeconds(offsetSeconds);
+ updatedOn = updatedOn.minusSeconds(offsetSeconds);
+ }
+
+ private boolean notifyIsNotRemoved(final Map lifetimeActions) {
+ return !this.lifetimeActions.containsKey(LifetimeActionType.NOTIFY)
+ || lifetimeActions.containsKey(LifetimeActionType.NOTIFY);
+ }
+}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImpl.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImpl.java
index 130d0946..87475867 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImpl.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImpl.java
@@ -11,8 +11,11 @@
import com.github.nagyesta.lowkeyvault.service.common.impl.BaseVaultFakeImpl;
import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.RotationPolicy;
import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId;
+import com.github.nagyesta.lowkeyvault.service.key.util.PeriodUtil;
import com.github.nagyesta.lowkeyvault.service.vault.VaultFake;
import lombok.NonNull;
import org.springframework.util.Assert;
@@ -20,6 +23,9 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
public class KeyVaultFakeImpl
extends BaseVaultFakeImpl>
@@ -29,6 +35,8 @@ public class KeyVaultFakeImpl
private final EcJsonWebKeyImportRequestConverter ecConverter = new EcJsonWebKeyImportRequestConverter();
private final AesJsonWebKeyImportRequestConverter aesConverter = new AesJsonWebKeyImportRequestConverter();
+ private final ConcurrentMap rotationPolicies = new ConcurrentHashMap<>();
+
public KeyVaultFakeImpl(@org.springframework.lang.NonNull final VaultFake vaultFake,
@org.springframework.lang.NonNull final RecoveryLevel recoveryLevel,
final Integer recoverableDays) {
@@ -67,6 +75,7 @@ public VersionedKeyEntityId importRsaKeyVersion(
Assert.isTrue(keyType.isRsa(), "RSA key expected, but found: " + keyType.name());
final RsaKeyVaultKeyEntity keyEntity = new RsaKeyVaultKeyEntity(keyEntityId, vaultFake(), rsaConverter.convert(key),
rsaConverter.getKeyParameter(key), keyType.isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -77,6 +86,7 @@ public VersionedKeyEntityId importEcKeyVersion(
Assert.isTrue(keyType.isEc(), "EC key expected, but found: " + keyType.name());
final EcKeyVaultKeyEntity keyEntity = new EcKeyVaultKeyEntity(keyEntityId, vaultFake(), ecConverter.convert(key),
ecConverter.getKeyParameter(key), keyType.isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -88,6 +98,7 @@ public VersionedKeyEntityId importOctKeyVersion(
Assert.isTrue(keyType.isHsm(), "OCT keys are only supported using HSM.");
final AesKeyVaultKeyEntity keyEntity = new AesKeyVaultKeyEntity(keyEntityId, vaultFake(), aesConverter.convert(key),
aesConverter.getKeyParameter(key), keyType.isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -97,6 +108,7 @@ public VersionedKeyEntityId createRsaKeyVersion(
final VersionedKeyEntityId keyEntityId = new VersionedKeyEntityId(vaultFake().baseUri(), keyName);
final RsaKeyVaultKeyEntity keyEntity = new RsaKeyVaultKeyEntity(keyEntityId, vaultFake(),
input.getKeyParameter(), input.getPublicExponent(), input.getKeyType().isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -107,6 +119,7 @@ public VersionedKeyEntityId createEcKeyVersion(
input.getKeyType().validate(input.getKeyParameter(), KeyCurveName.class);
final EcKeyVaultKeyEntity keyEntity = new EcKeyVaultKeyEntity(keyEntityId, vaultFake(),
input.getKeyParameter(), input.getKeyType().isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -117,6 +130,7 @@ public VersionedKeyEntityId createOctKeyVersion(
Assert.isTrue(input.getKeyType().isHsm(), "OCT keys are only supported using HSM.");
final AesKeyVaultKeyEntity keyEntity = new AesKeyVaultKeyEntity(keyEntityId, vaultFake(),
input.getKeyParameter(), input.getKeyType().isHsm());
+ setExpiryBasedOnRotationPolicy(keyEntityId, keyEntity);
return addVersion(keyEntityId, keyEntity);
}
@@ -126,4 +140,51 @@ public void setKeyOperations(@NonNull final VersionedKeyEntityId keyEntityId,
getEntitiesInternal().getEntity(keyEntityId).setOperations(Objects.requireNonNullElse(keyOperations, Collections.emptyList()));
}
+ @Override
+ public void timeShift(final int offsetSeconds) {
+ super.timeShift(offsetSeconds);
+ rotationPolicies.values().forEach(p -> p.timeShift(offsetSeconds));
+ }
+
+ @Override
+ public RotationPolicy rotationPolicy(@NonNull final KeyEntityId keyEntityId) {
+ return rotationPolicies.get(keyEntityId.id());
+ }
+
+ @Override
+ public void setRotationPolicy(@NonNull final RotationPolicy rotationPolicy) {
+ final ReadOnlyKeyVaultKeyEntity readOnlyEntity = latestReadOnlyKeyVersion(rotationPolicy.getId());
+ rotationPolicy.validate(readOnlyEntity.getExpiry().orElse(null));
+ final RotationPolicy existingPolicy = rotationPolicy(rotationPolicy.getId());
+ if (existingPolicy == null) {
+ rotationPolicies.put(rotationPolicy.getId().id(), rotationPolicy);
+ } else {
+ existingPolicy.setLifetimeActions(rotationPolicy.getLifetimeActions());
+ existingPolicy.setExpiryTime(rotationPolicy.getExpiryTime());
+ }
+ }
+
+ @Override
+ public VersionedKeyEntityId rotateKey(@NonNull final KeyEntityId keyEntityId) {
+ final ReadOnlyKeyVaultKeyEntity readOnlyEntity = latestReadOnlyKeyVersion(keyEntityId);
+ final VersionedKeyEntityId rotatedKeyId = createKeyVersion(keyEntityId.id(), readOnlyEntity.keyCreationInput());
+ final KeyVaultKeyEntity, ?> rotatedEntity = getEntities().getEntity(rotatedKeyId, KeyVaultKeyEntity.class);
+ rotatedEntity.setOperations(readOnlyEntity.getOperations());
+ rotatedEntity.setEnabled(true);
+ rotatedEntity.setTags(readOnlyEntity.getTags());
+ return rotatedKeyId;
+ }
+
+ private void setExpiryBasedOnRotationPolicy(final VersionedKeyEntityId keyEntityId, final KeyVaultKeyEntity, ?> keyEntity) {
+ final Optional expiryDays = Optional.ofNullable(rotationPolicies)
+ .map(policies -> policies.get(keyEntityId.id()))
+ .map(ReadOnlyRotationPolicy::getExpiryTime)
+ .map(PeriodUtil::asDays);
+ expiryDays.ifPresent(days -> keyEntity.setExpiry(keyEntity.getCreated().plusDays(days)));
+ }
+
+ private ReadOnlyKeyVaultKeyEntity latestReadOnlyKeyVersion(final KeyEntityId keyEntityId) {
+ final VersionedKeyEntityId latestVersionOfEntity = getEntities().getLatestVersionOfEntity(keyEntityId);
+ return getEntities().getReadOnlyEntity(latestVersionOfEntity);
+ }
}
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntity.java
index 1d626c1f..97885f89 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntity.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntity.java
@@ -14,6 +14,11 @@
import java.util.List;
import java.util.concurrent.Callable;
+/**
+ * Common Key entity base class.
+ * @param The type of the key.
+ * @param The type of the key parameter.
+ */
public abstract class KeyVaultKeyEntity extends KeyVaultBaseEntity implements ReadOnlyKeyVaultKeyEntity {
private final T key;
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java
index 365b6024..d01d6b9a 100644
--- a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntity.java
@@ -49,6 +49,11 @@ public KeyType getKeyType() {
}
}
+ @Override
+ public KeyCreationInput> keyCreationInput() {
+ return new RsaKeyCreationInput(getKeyType(), getKeySize(), ((RSAPublicKey) getKey().getPublic()).getPublicExponent());
+ }
+
@Override
public byte[] getN() {
return ((RSAPublicKey) getKey().getPublic()).getModulus().toByteArray();
diff --git a/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtil.java b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtil.java
new file mode 100644
index 00000000..123572a3
--- /dev/null
+++ b/lowkey-vault-app/src/main/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtil.java
@@ -0,0 +1,22 @@
+package com.github.nagyesta.lowkeyvault.service.key.util;
+
+import lombok.NonNull;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.time.temporal.ChronoUnit;
+
+public final class PeriodUtil {
+
+ private PeriodUtil() {
+ throw new IllegalCallerException("Utility cannot be instantiated.");
+ }
+
+ public static long asDays(@NonNull final Period period) {
+ return asDays(period, OffsetDateTime.now());
+ }
+
+ static long asDays(final Period period, final OffsetDateTime relativeTo) {
+ return ChronoUnit.DAYS.between(relativeTo, relativeTo.plus(period));
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreControllerIntegrationTest.java
index 95330412..e7b3a6ce 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreControllerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/KeyBackupRestoreControllerIntegrationTest.java
@@ -32,6 +32,7 @@
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -110,7 +111,7 @@ void testRestoreEntityShouldRestoreASingleKeyWhenCalledWithValidInput() {
}
@Test
- void testRestoreEntityShouldRestoreAThreeKeysWhenCalledWithValidInput() {
+ void testRestoreEntityShouldRestoreThreeKeysWhenCalledWithValidInput() {
//given
final KeyBackupModel backupModel = new KeyBackupModel();
backupModel.setValue(new KeyBackupList());
@@ -270,7 +271,9 @@ private KeyPair addVersionToList(final URI baseUri, final String name, final Str
propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE);
listItem.setAttributes(propertiesModel);
listItem.setTags(tags);
- backupModel.getValue().add(listItem);
+ final List list = new ArrayList<>(backupModel.getValue().getVersions());
+ list.add(listItem);
+ backupModel.getValue().setVersions(list);
return keyPair;
}
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretBackupRestoreControllerIntegrationTest.java
index 2c1e99bf..6acb12a6 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretBackupRestoreControllerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_2/SecretBackupRestoreControllerIntegrationTest.java
@@ -23,6 +23,8 @@
import org.springframework.util.MimeTypeUtils;
import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
@@ -244,6 +246,8 @@ private void addVersionToList(final URI baseUri, final String name, final String
propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE);
listItem.setAttributes(propertiesModel);
listItem.setTags(tags);
- backupModel.getValue().add(listItem);
+ final List list = new ArrayList<>(backupModel.getValue().getVersions());
+ list.add(listItem);
+ backupModel.getValue().setVersions(list);
}
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreControllerIntegrationTest.java
index 5b3ab517..00a33414 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreControllerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyBackupRestoreControllerIntegrationTest.java
@@ -3,14 +3,23 @@
import com.github.nagyesta.lowkeyvault.TestConstantsUri;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72BackupConverter;
import com.github.nagyesta.lowkeyvault.mapper.v7_2.key.KeyEntityToV72ModelConverter;
+import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyToV73ModelConverter;
+import com.github.nagyesta.lowkeyvault.mapper.v7_3.key.KeyRotationPolicyV73ModelToEntityConverter;
import com.github.nagyesta.lowkeyvault.model.v7_2.common.constants.RecoveryLevel;
-import com.github.nagyesta.lowkeyvault.model.v7_2.key.*;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyBackupListItem;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyPropertiesModel;
+import com.github.nagyesta.lowkeyvault.model.v7_2.key.KeyVaultKeyModel;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyCurveName;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyOperation;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.request.JsonWebKeyImportRequest;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.*;
import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException;
import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId;
import com.github.nagyesta.lowkeyvault.service.key.impl.EcKeyCreationInput;
import com.github.nagyesta.lowkeyvault.service.key.util.KeyGenUtil;
@@ -32,23 +41,29 @@
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.time.Period;
+import java.util.*;
import java.util.stream.Stream;
import static com.github.nagyesta.lowkeyvault.TestConstants.*;
import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.*;
+import static com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType.ROTATE;
import static org.mockito.Mockito.mock;
@SpringBootTest
class KeyBackupRestoreControllerIntegrationTest {
+ private static final Period EXPIRY_TIME = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+ private static final Period TRIGGER_TIME = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY);
@Autowired
@Qualifier("KeyBackupRestoreControllerV73")
private KeyBackupRestoreController underTest;
@Autowired
private VaultService vaultService;
+ @Autowired
+ private KeyRotationPolicyToV73ModelConverter toModelConverter;
+ @Autowired
+ private KeyRotationPolicyV73ModelToEntityConverter toEntityConverter;
private URI uri;
public static Stream nullProvider() {
@@ -86,7 +101,7 @@ void testConstructorShouldThrowExceptionWhenCalledWithNulls(
//when
Assertions.assertThrows(IllegalArgumentException.class,
- () -> new KeyBackupRestoreController(modelConverter, backupConverter, vaultService));
+ () -> new KeyBackupRestoreController(modelConverter, backupConverter, vaultService, toModelConverter, toEntityConverter));
//then + exception
}
@@ -110,7 +125,7 @@ void testRestoreEntityShouldRestoreASingleKeyWhenCalledWithValidInput() {
}
@Test
- void testRestoreEntityShouldRestoreAThreeKeysWhenCalledWithValidInput() {
+ void testRestoreEntityShouldRestoreThreeKeysWhenCalledWithValidInput() {
//given
final KeyBackupModel backupModel = new KeyBackupModel();
backupModel.setValue(new KeyBackupList());
@@ -129,6 +144,36 @@ void testRestoreEntityShouldRestoreAThreeKeysWhenCalledWithValidInput() {
assertRestoredKeyMatchesExpectations(actualBody, (ECPublicKey) expectedKey.getPublic(), KEY_VERSION_3, TAGS_EMPTY);
}
+ @Test
+ void testRestoreEntityShouldRestoreRotationPolicyWhenCalledWithValidInput() {
+ //given
+ final KeyBackupModel backupModel = new KeyBackupModel();
+ backupModel.setValue(new KeyBackupList());
+ final KeyPair expectedKey = addVersionToList(uri, KEY_NAME_1, KEY_VERSION_1, backupModel, TAGS_EMPTY);
+ final KeyEntityId keyEntityId = new KeyEntityId(uri, KEY_NAME_1);
+ backupModel.getValue().setKeyRotationPolicy(keyRotationPolicy(keyEntityId));
+
+ //when
+ final ResponseEntity actual = underTest.restore(uri, backupModel);
+
+ //then
+ Assertions.assertNotNull(actual);
+ final KeyVaultKeyModel actualBody = actual.getBody();
+ Assertions.assertNotNull(actualBody);
+ Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode());
+ assertRestoredKeyMatchesExpectations(actualBody, (ECPublicKey) expectedKey.getPublic(), KEY_VERSION_1, TAGS_EMPTY);
+ final ReadOnlyRotationPolicy rotationPolicy = vaultService.findByUri(uri).keyVaultFake().rotationPolicy(keyEntityId);
+ Assertions.assertEquals(keyEntityId, rotationPolicy.getId());
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, rotationPolicy.getCreatedOn());
+ Assertions.assertEquals(NOW, rotationPolicy.getUpdatedOn());
+ Assertions.assertEquals(EXPIRY_TIME, rotationPolicy.getExpiryTime());
+ Assertions.assertIterableEquals(Collections.singleton(ROTATE), rotationPolicy.getLifetimeActions().keySet());
+ final LifetimeAction lifetimeAction = rotationPolicy.getLifetimeActions().get(ROTATE);
+ Assertions.assertEquals(ROTATE, lifetimeAction.getActionType());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_AFTER_CREATE, lifetimeAction.getTrigger().getTriggerType());
+ Assertions.assertEquals(TRIGGER_TIME, lifetimeAction.getTrigger().getTimePeriod());
+ }
+
@Test
void testRestoreEntityShouldThrowExceptionWhenCalledWithMoreThanOneUris() {
//given
@@ -270,7 +315,32 @@ private KeyPair addVersionToList(final URI baseUri, final String name, final Str
propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE);
listItem.setAttributes(propertiesModel);
listItem.setTags(tags);
- backupModel.getValue().add(listItem);
+ final List list = new ArrayList<>(backupModel.getValue().getVersions());
+ list.add(listItem);
+ backupModel.getValue().setVersions(list);
return keyPair;
}
+
+ private KeyRotationPolicyModel keyRotationPolicy(final KeyEntityId keyEntityId) {
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setLifetimeActions(List.of(actionModel()));
+ model.setAttributes(rotationPolicyAttributes());
+ return model;
+ }
+
+ private KeyRotationPolicyAttributes rotationPolicyAttributes() {
+ final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes();
+ attributes.setCreatedOn(TIME_10_MINUTES_AGO);
+ attributes.setUpdatedOn(NOW);
+ attributes.setExpiryTime(EXPIRY_TIME);
+ return attributes;
+ }
+
+ private KeyLifetimeActionModel actionModel() {
+ final KeyLifetimeActionModel actionModel = new KeyLifetimeActionModel();
+ actionModel.setAction(new KeyLifetimeActionTypeModel(ROTATE));
+ actionModel.setTrigger(new KeyLifetimeActionTriggerModel(null, TRIGGER_TIME));
+ return actionModel;
+ }
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java
index c94ff1da..57418360 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/KeyControllerTest.java
@@ -905,6 +905,39 @@ void testUpdateVersionShouldReturnEntryWhenKeyAndVersionIsFound(
verify(keyEntityToV72ModelConverter).convert(same(entity));
}
+ @Test
+ void testRotateKeyShouldCallTheKeyVaultFakeForDoingTheRotationWhenCalled() {
+ //given
+ final KeyEntityId entityId = new KeyEntityId(HTTPS_LOCALHOST_8443, KEY_NAME_1, null);
+ final VersionedKeyEntityId newKeyId = new VersionedKeyEntityId(entityId.vault(), entityId.id());
+ final CreateKeyRequest request = createRequest(List.of(), null, null);
+ final ReadOnlyKeyVaultKeyEntity entity = createEntity(newKeyId, request);
+ when(keyVaultFake.rotateKey(eq(entityId)))
+ .thenReturn(newKeyId);
+ when(keyVaultFake.getEntities())
+ .thenReturn(entities);
+ when(entities.getReadOnlyEntity(eq(newKeyId)))
+ .thenReturn(entity);
+ when(keyEntityToV72ModelConverter.convert(same(entity)))
+ .thenReturn(RESPONSE);
+
+ //when
+ final ResponseEntity actual = underTest.rotateKey(entityId.id(), entityId.vault());
+
+ //then
+ Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode());
+ Assertions.assertEquals(RESPONSE, actual.getBody());
+ final InOrder inOrder = inOrder(keyVaultFake, entities, keyEntityToV72ModelConverter);
+ inOrder.verify(keyVaultFake)
+ .rotateKey(eq(entityId));
+ inOrder.verify(keyVaultFake)
+ .getEntities();
+ inOrder.verify(entities)
+ .getReadOnlyEntity(eq(newKeyId));
+ inOrder.verify(keyEntityToV72ModelConverter)
+ .convert(same(entity));
+ }
+
@NonNull
private CreateKeyRequest createRequest(
final List operations,
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretBackupRestoreControllerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretBackupRestoreControllerIntegrationTest.java
index 619c8668..39de3b36 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretBackupRestoreControllerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/controller/v7_3/SecretBackupRestoreControllerIntegrationTest.java
@@ -23,6 +23,8 @@
import org.springframework.util.MimeTypeUtils;
import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
@@ -244,6 +246,8 @@ private void addVersionToList(final URI baseUri, final String name, final String
propertiesModel.setRecoverableDays(RecoveryLevel.MAX_RECOVERABLE_DAYS_INCLUSIVE);
listItem.setAttributes(propertiesModel);
listItem.setTags(tags);
- backupModel.getValue().add(listItem);
+ final List list = new ArrayList<>(backupModel.getValue().getVersions());
+ list.add(listItem);
+ backupModel.getValue().setVersions(list);
}
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72PropertiesModelConverterTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72PropertiesModelConverterTest.java
index 6e234366..6388fa67 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72PropertiesModelConverterTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_2/key/KeyEntityToV72PropertiesModelConverterTest.java
@@ -6,6 +6,7 @@
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.SignatureAlgorithm;
import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyCreationInput;
import com.github.nagyesta.lowkeyvault.service.key.impl.KeyVaultKeyEntity;
import com.github.nagyesta.lowkeyvault.service.vault.VaultFake;
import org.junit.jupiter.api.AfterEach;
@@ -108,6 +109,11 @@ public KeyType getKeyType() {
return null;
}
+ @Override
+ public KeyCreationInput> keyCreationInput() {
+ return null;
+ }
+
@Override
public byte[] encryptBytes(final byte[] clear, final EncryptionAlgorithm encryptionAlgorithm,
final byte[] iv) {
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverterTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverterTest.java
new file mode 100644
index 00000000..d08c0912
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyToV73ModelConverterTest.java
@@ -0,0 +1,53 @@
+package com.github.nagyesta.lowkeyvault.mapper.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.TestConstantsKeys;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyLifetimeActionModel;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyRotationPolicyModel;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.KeyLifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyLifetimeActionTrigger;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyRotationPolicy;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.Period;
+import java.util.Map;
+
+import static com.github.nagyesta.lowkeyvault.TestConstants.TIME_10_MINUTES_AGO;
+import static com.github.nagyesta.lowkeyvault.TestConstants.TIME_IN_10_MINUTES;
+
+class KeyRotationPolicyToV73ModelConverterTest {
+
+ @Test
+ void testConvertShouldConvertValuableFieldsWhenCalledWithValidData() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+ final Period expiryTime = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+ final Period triggerPeriod = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+
+ final KeyLifetimeActionTrigger trigger = new KeyLifetimeActionTrigger(triggerPeriod, LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+ final KeyRotationPolicy source = new KeyRotationPolicy(keyEntityId, expiryTime,
+ Map.of(LifetimeActionType.NOTIFY, new KeyLifetimeAction(LifetimeActionType.NOTIFY, trigger)));
+ source.setCreatedOn(TIME_10_MINUTES_AGO);
+ source.setUpdatedOn(TIME_IN_10_MINUTES);
+
+ final KeyRotationPolicyToV73ModelConverter underTest = new KeyRotationPolicyToV73ModelConverter();
+
+ //when
+ final KeyRotationPolicyModel actual = underTest.convert(source);
+
+ //then
+ Assertions.assertNotNull(actual);
+ Assertions.assertEquals(keyEntityId.asRotationPolicyUri(), actual.getId());
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, actual.getAttributes().getCreatedOn());
+ Assertions.assertEquals(TIME_IN_10_MINUTES, actual.getAttributes().getUpdatedOn());
+ Assertions.assertEquals(expiryTime, actual.getAttributes().getExpiryTime());
+ Assertions.assertEquals(1, actual.getLifetimeActions().size());
+ final KeyLifetimeActionModel actionModel = actual.getLifetimeActions().get(0);
+ Assertions.assertEquals(triggerPeriod, actionModel.getTrigger().getTriggerPeriod());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_BEFORE_EXPIRY, actionModel.getTrigger().getTriggerType());
+ Assertions.assertEquals(LifetimeActionType.NOTIFY, actionModel.getAction().getType());
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverterTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverterTest.java
new file mode 100644
index 00000000..f879b455
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/mapper/v7_3/key/KeyRotationPolicyV73ModelToEntityConverterTest.java
@@ -0,0 +1,164 @@
+package com.github.nagyesta.lowkeyvault.mapper.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.TestConstantsKeys;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.*;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.RotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.Period;
+import java.util.List;
+
+import static com.github.nagyesta.lowkeyvault.TestConstants.NOW;
+import static com.github.nagyesta.lowkeyvault.TestConstants.TIME_10_MINUTES_AGO;
+
+class KeyRotationPolicyV73ModelToEntityConverterTest {
+
+ @Test
+ void testConvertShouldConvertValuableFieldsWhenCalledWithValidData() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+ final Period timeBeforeExpiry = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ final Period expiryTime = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setAttributes(attributes(expiryTime));
+ model.setLifetimeActions(List.of(notifyAction(timeBeforeExpiry)));
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ final RotationPolicy actual = underTest.convert(keyEntityId, model);
+
+ //then
+ Assertions.assertEquals(keyEntityId, actual.getId());
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, actual.getCreatedOn());
+ Assertions.assertEquals(NOW, actual.getUpdatedOn());
+ Assertions.assertEquals(expiryTime, actual.getExpiryTime());
+ final LifetimeAction actualNotify = actual.getLifetimeActions().get(LifetimeActionType.NOTIFY);
+ Assertions.assertEquals(timeBeforeExpiry, actualNotify.getTrigger().getTimePeriod());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_BEFORE_EXPIRY, actualNotify.getTrigger().getTriggerType());
+ Assertions.assertEquals(LifetimeActionType.NOTIFY, actualNotify.getActionType());
+ }
+
+ @Test
+ void testConvertShouldUseDefaultsWhenCalledWithMinimalAttributes() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+ final Period expiryTime = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+ final Period timeBeforeExpiry = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setLifetimeActions(List.of(notifyAction(timeBeforeExpiry)));
+ final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes();
+ attributes.setExpiryTime(expiryTime);
+ model.setAttributes(attributes);
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ final RotationPolicy actual = underTest.convert(keyEntityId, model);
+
+ //then
+ Assertions.assertEquals(keyEntityId, actual.getId());
+ Assertions.assertTrue(actual.getCreatedOn().isAfter(NOW));
+ Assertions.assertTrue(actual.getUpdatedOn().isAfter(NOW));
+ Assertions.assertEquals(expiryTime, actual.getExpiryTime());
+ final LifetimeAction actualNotify = actual.getLifetimeActions().get(LifetimeActionType.NOTIFY);
+ Assertions.assertEquals(timeBeforeExpiry, actualNotify.getTrigger().getTimePeriod());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_BEFORE_EXPIRY, actualNotify.getTrigger().getTriggerType());
+ Assertions.assertEquals(LifetimeActionType.NOTIFY, actualNotify.getActionType());
+ }
+
+ @Test
+ void testConvertShouldThrowExceptionWhenCalledWithoutAttributes() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+ final Period timeBeforeExpiry = Period.ofDays(LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setLifetimeActions(List.of(notifyAction(timeBeforeExpiry)));
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.convert(keyEntityId, model));
+
+ //then + exception
+ }
+
+ @Test
+ void testConvertShouldReturnNullWhenCalledWithEmptyModel() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ final RotationPolicy actual = underTest.convert(keyEntityId, model);
+
+ //then
+ Assertions.assertNull(actual);
+ }
+
+ @Test
+ void testConvertShouldThrowExceptionWhenCalledWithNoList() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+ final Period expiryTime = Period.ofDays(LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setAttributes(attributes(expiryTime));
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.convert(keyEntityId, model));
+
+ //then + exception
+ }
+
+ @Test
+ void testConvertShouldReturnNullWhenCalledWithoutAttributesAndEmptyList() {
+ //given
+ final KeyEntityId keyEntityId = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(keyEntityId.asRotationPolicyUri());
+ model.setLifetimeActions(List.of());
+
+ final KeyRotationPolicyV73ModelToEntityConverter underTest = new KeyRotationPolicyV73ModelToEntityConverter();
+
+ //when
+ final RotationPolicy actual = underTest.convert(keyEntityId, model);
+
+ //then
+ Assertions.assertNull(actual);
+ }
+
+ private KeyLifetimeActionModel notifyAction(final Period timeBeforeExpiry) {
+ final KeyLifetimeActionModel notify = new KeyLifetimeActionModel();
+ notify.setTrigger(new KeyLifetimeActionTriggerModel(timeBeforeExpiry, null));
+ notify.setAction(new KeyLifetimeActionTypeModel(LifetimeActionType.NOTIFY));
+ return notify;
+ }
+
+ private KeyRotationPolicyAttributes attributes(final Period expiryTime) {
+ final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes();
+ attributes.setCreatedOn(TIME_10_MINUTES_AGO);
+ attributes.setUpdatedOn(NOW);
+ attributes.setExpiryTime(expiryTime);
+ return attributes;
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerDeserializerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerDeserializerIntegrationTest.java
index 6d9baee5..7ae5637e 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerDeserializerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerDeserializerIntegrationTest.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.security.KeyPair;
+import java.util.List;
import java.util.Map;
import static com.github.nagyesta.lowkeyvault.TestConstants.*;
@@ -74,7 +75,7 @@ void testSerializeShouldConvertContentWhenCalledWithValidValue() throws IOExcept
private KeyBackupModel getKeyBackupModel(final KeyBackupListItem item) {
final KeyBackupList list = new KeyBackupList();
- list.add(item);
+ list.setVersions(List.of(item));
final KeyBackupModel input = new KeyBackupModel();
input.setValue(list);
return input;
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipSecretSerializerDeserializerIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipSecretSerializerDeserializerIntegrationTest.java
index c02d0a20..3a4ed1ca 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipSecretSerializerDeserializerIntegrationTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipSecretSerializerDeserializerIntegrationTest.java
@@ -15,6 +15,7 @@
import org.springframework.util.MimeTypeUtils;
import java.io.IOException;
+import java.util.List;
import java.util.Map;
import static com.github.nagyesta.lowkeyvault.TestConstants.*;
@@ -68,7 +69,7 @@ void testSerializeShouldConvertContentWhenCalledWithValidValue() throws IOExcept
private SecretBackupModel getSecretBackupModel(final SecretBackupListItem item) {
final SecretBackupList list = new SecretBackupList();
- list.add(item);
+ list.setVersions(List.of(item));
final SecretBackupModel input = new SecretBackupModel();
input.setValue(list);
return input;
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializerTest.java
similarity index 87%
rename from lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializerTest.java
rename to lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializerTest.java
index 10a494d9..e577752f 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeyDeserializerTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeyDeserializerTest.java
@@ -11,14 +11,14 @@
import static org.mockito.Mockito.*;
-class Base64ZipKeyDeserializerTest {
+class Base64ZipV72KeyDeserializerTest {
@Test
void testDeserializeShouldThrowExceptionWhenDecodingFails() throws IOException {
//given
final Base64Deserializer base64Deserializer = mock(Base64Deserializer.class);
final ObjectMapper objectMapper = mock(ObjectMapper.class);
- final Base64ZipKeyDeserializer underTest = new Base64ZipKeyDeserializer(base64Deserializer, objectMapper);
+ final Base64ZipV72KeyDeserializer underTest = new Base64ZipV72KeyDeserializer(base64Deserializer, objectMapper);
final JsonParser jsonParser = mock(JsonParser.class);
final DeserializationContext context = mock(DeserializationContext.class);
when(base64Deserializer.deserializeBase64(eq(jsonParser))).thenReturn(new byte[1]);
@@ -34,7 +34,7 @@ void testDeserializeShouldThrowExceptionWhenDecodingFails() throws IOException {
@Test
void testDeserializeShouldWriteNullWhenCalledWithNullInput() throws IOException {
//given
- final Base64ZipKeyDeserializer underTest = new Base64ZipKeyDeserializer();
+ final Base64ZipV72KeyDeserializer underTest = new Base64ZipV72KeyDeserializer();
final JsonParser jsonParser = mock(JsonParser.class);
when(jsonParser.readValueAs(eq(String.class))).thenReturn("");
final DeserializationContext context = mock(DeserializationContext.class);
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializerTest.java
similarity index 87%
rename from lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerTest.java
rename to lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializerTest.java
index ed93b6bb..2b90977a 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipKeySerializerTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV72KeySerializerTest.java
@@ -11,14 +11,14 @@
import static org.mockito.Mockito.*;
-class Base64ZipKeySerializerTest {
+class Base64ZipV72KeySerializerTest {
@Test
void testSerializeShouldThrowExceptionWhenEncodingFails() {
//given
final Base64Serializer base64Serializer = mock(Base64Serializer.class);
final ObjectMapper objectMapper = new ObjectMapper();
- final Base64ZipKeySerializer underTest = new Base64ZipKeySerializer(base64Serializer, objectMapper);
+ final Base64ZipV72KeySerializer underTest = new Base64ZipV72KeySerializer(base64Serializer, objectMapper);
final JsonGenerator gen = mock(JsonGenerator.class);
final SerializerProvider serializers = mock(SerializerProvider.class);
when(base64Serializer.base64Encode(any())).thenThrow(new IllegalStateException("Fail"));
@@ -34,7 +34,7 @@ void testSerializeShouldThrowExceptionWhenEncodingFails() {
@Test
void testSerializeShouldWriteNullWhenCalledWithNullInput() throws IOException {
//given
- final Base64ZipKeySerializer underTest = new Base64ZipKeySerializer();
+ final Base64ZipV72KeySerializer underTest = new Base64ZipV72KeySerializer();
final JsonGenerator gen = mock(JsonGenerator.class);
final SerializerProvider serializers = mock(SerializerProvider.class);
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializerTest.java
new file mode 100644
index 00000000..603ec4bf
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeyDeserializerTest.java
@@ -0,0 +1,61 @@
+package com.github.nagyesta.lowkeyvault.model.json.util;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.*;
+
+class Base64ZipV73KeyDeserializerTest {
+
+ @Test
+ void testDeserializeShouldThrowExceptionWhenDecodingFails() throws IOException {
+ //given
+ final Base64Deserializer base64Deserializer = mock(Base64Deserializer.class);
+ final ObjectMapper objectMapper = mock(ObjectMapper.class);
+ final Base64ZipV73KeyDeserializer underTest = new Base64ZipV73KeyDeserializer(base64Deserializer, objectMapper);
+ final JsonParser jsonParser = mock(JsonParser.class);
+ final DeserializationContext context = mock(DeserializationContext.class);
+ when(base64Deserializer.deserializeBase64(eq(jsonParser))).thenReturn(new byte[1]);
+ when(objectMapper.reader()).thenThrow(new IllegalStateException("Fail"));
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.deserialize(jsonParser, context));
+
+ //then + exception
+ verify(base64Deserializer).deserializeBase64(eq(jsonParser));
+ }
+
+ @Test
+ void testDeserializeShouldWriteNullWhenCalledWithNullInput() throws IOException {
+ //given
+ final Base64ZipV73KeyDeserializer underTest = new Base64ZipV73KeyDeserializer();
+ final JsonParser jsonParser = mock(JsonParser.class);
+ when(jsonParser.readValueAs(eq(String.class))).thenReturn("");
+ final DeserializationContext context = mock(DeserializationContext.class);
+
+ //when
+ final KeyBackupList actual = underTest.deserialize(jsonParser, context);
+
+ //then
+ Assertions.assertNull(actual);
+ verify(jsonParser).readValueAs(eq(String.class));
+ }
+
+ @Test
+ void testGetTypeShouldReturnCorrectTypeWhenCalled() {
+ //given
+ final Base64ZipV73KeyDeserializer underTest = new Base64ZipV73KeyDeserializer();
+
+ //when
+ final Class actual = underTest.getType();
+
+ //then
+ Assertions.assertEquals(KeyBackupList.class, actual);
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializerTest.java
new file mode 100644
index 00000000..1b3a3132
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/json/util/Base64ZipV73KeySerializerTest.java
@@ -0,0 +1,48 @@
+package com.github.nagyesta.lowkeyvault.model.json.util;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.KeyBackupList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.*;
+
+class Base64ZipV73KeySerializerTest {
+
+ @Test
+ void testSerializeShouldThrowExceptionWhenEncodingFails() {
+ //given
+ final Base64Serializer base64Serializer = mock(Base64Serializer.class);
+ final ObjectMapper objectMapper = new ObjectMapper();
+ final Base64ZipV73KeySerializer underTest = new Base64ZipV73KeySerializer(base64Serializer, objectMapper);
+ final JsonGenerator gen = mock(JsonGenerator.class);
+ final SerializerProvider serializers = mock(SerializerProvider.class);
+ when(base64Serializer.base64Encode(any())).thenThrow(new IllegalStateException("Fail"));
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.serialize(new KeyBackupList(), gen, serializers));
+
+ //then + exception
+ verify(base64Serializer).base64Encode(any());
+ verifyNoInteractions(gen, serializers);
+ }
+
+ @Test
+ void testSerializeShouldWriteNullWhenCalledWithNullInput() throws IOException {
+ //given
+ final Base64ZipV73KeySerializer underTest = new Base64ZipV73KeySerializer();
+ final JsonGenerator gen = mock(JsonGenerator.class);
+ final SerializerProvider serializers = mock(SerializerProvider.class);
+
+ //when
+ underTest.serialize(null, gen, serializers);
+
+ //then
+ verify(gen).writeNull();
+ verify(gen, never()).writeString(anyString());
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModelTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModelTest.java
new file mode 100644
index 00000000..8c87f5b8
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTriggerModelTest.java
@@ -0,0 +1,68 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.time.Period;
+
+class KeyLifetimeActionTriggerModelTest {
+
+ @Test
+ void testJsonConstructorShouldThrowExceptionWhenBothInputsAreNull() {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
+ new KeyLifetimeActionTriggerModel(null, null));
+
+ //then + exception
+ }
+
+ @Test
+ void testJsonConstructorShouldThrowExceptionWhenBothInputsArePresent() {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
+ new KeyLifetimeActionTriggerModel(Period.ZERO, Period.ZERO));
+
+ //then + exception
+ }
+
+ @Test
+ void testJsonConstructorShouldSetBeforeExpiryWhenItIsPopulated() {
+ //given
+
+ //when
+ final KeyLifetimeActionTriggerModel actual = new KeyLifetimeActionTriggerModel(Period.ZERO, null);
+
+ //then
+ Assertions.assertEquals(Period.ZERO, actual.getTriggerPeriod());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_BEFORE_EXPIRY, actual.getTriggerType());
+ }
+
+ @Test
+ void testJsonConstructorShouldSetAfterCreateWhenItIsPopulated() {
+ //given
+
+ //when
+ final KeyLifetimeActionTriggerModel actual = new KeyLifetimeActionTriggerModel(null, Period.ZERO);
+
+ //then
+ Assertions.assertEquals(Period.ZERO, actual.getTriggerPeriod());
+ Assertions.assertEquals(LifetimeActionTriggerType.TIME_AFTER_CREATE, actual.getTriggerType());
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testConstructorShouldThrowExceptionWhenCalledWithNull() {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
+ new KeyLifetimeActionTriggerModel(null));
+
+ //then + exception
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModelTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModelTest.java
new file mode 100644
index 00000000..db0a9ffb
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyLifetimeActionTypeModelTest.java
@@ -0,0 +1,31 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class KeyLifetimeActionTypeModelTest {
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testConstructorShouldThrowExceptionWhenCalledWithNull() {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new KeyLifetimeActionTypeModel(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testConstructorShouldSetActionTypeWhenCalledWithValidValue() {
+ //given
+ final LifetimeActionType expected = LifetimeActionType.NOTIFY;
+
+ //when
+ final KeyLifetimeActionTypeModel actual = new KeyLifetimeActionTypeModel(expected);
+
+ //then
+ Assertions.assertEquals(expected, actual.getType());
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModelIntegrationTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModelIntegrationTest.java
new file mode 100644
index 00000000..a3e64c5e
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/KeyRotationPolicyModelIntegrationTest.java
@@ -0,0 +1,165 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.nagyesta.lowkeyvault.ResourceUtils;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyLifetimeActionTrigger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validator;
+import java.io.IOException;
+import java.net.URI;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+
+@SpringBootTest
+@TestPropertySource(locations = "classpath:test-application.properties")
+class KeyRotationPolicyModelIntegrationTest {
+
+ private static final KeyLifetimeActionTrigger TRIGGER_90_DAYS_AFTER_CREATION =
+ new KeyLifetimeActionTrigger(Period.ofDays(90), LifetimeActionTriggerType.TIME_AFTER_CREATE);
+ private static final KeyLifetimeActionModel ROTATE_ACTION =
+ new KeyLifetimeActionModel(
+ new KeyLifetimeActionTypeModel(LifetimeActionType.ROTATE),
+ new KeyLifetimeActionTriggerModel(TRIGGER_90_DAYS_AFTER_CREATION));
+ private static final KeyLifetimeActionTrigger TRIGGER_30_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeActionTrigger(Period.ofDays(30), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+ private static final KeyLifetimeActionModel NOTIFY_ACTION =
+ new KeyLifetimeActionModel(
+ new KeyLifetimeActionTypeModel(LifetimeActionType.NOTIFY),
+ new KeyLifetimeActionTriggerModel(TRIGGER_30_DAYS_BEFORE_EXPIRY));
+ private static final String POLICY_URI_STRING = "https:/localhost:8443/keys/key-name/rotationpolicy";
+ private static final String MINIMUM_JSON = "/key/rotation/valid-rotation-policy-minimum.json";
+ private static final String FULL_JSON = "/key/rotation/valid-rotation-policy-full.json";
+ private static final Period EXPIRY_PERIOD_4M = Period.ofMonths(4);
+ private static final OffsetDateTime CREATED_ON = OffsetDateTime.ofInstant(Instant.ofEpochSecond(1482188947), ZoneOffset.UTC);
+ private static final OffsetDateTime UPDATED_ON = OffsetDateTime.ofInstant(Instant.ofEpochSecond(1482188948), ZoneOffset.UTC);
+ @Autowired
+ private Validator validator;
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ public static Stream invalidProvider() {
+ return Stream.builder()
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-empty-actions.json", "lifetimeActions"))
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-missing-attributes.json", "attributes"))
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-missing-expiry.json", "attributes.expiryTime"))
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-missing-actions.json", "lifetimeActions"))
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-null-action-type.json", "lifetimeActions[0].action.type"))
+ .add(Arguments.of("/key/rotation/invalid-rotation-policy-null-trigger.json", "lifetimeActions[0].trigger"))
+ .build();
+ }
+
+ @Test
+ void testJsonSerializationShouldContainAllValuableFieldsWhenCalledOnFullyPopulatedObject() throws JsonProcessingException {
+ //given
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(URI.create(POLICY_URI_STRING));
+ model.setLifetimeActions(List.of(ROTATE_ACTION, NOTIFY_ACTION));
+ model.setAttributes(policyAttributes(EXPIRY_PERIOD_4M));
+ final String expected = readResourceAsStringRemoveWhitespace(FULL_JSON);
+
+ //when
+ final String actual = objectMapper.writer().writeValueAsString(model);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ void testJsonSerializationShouldContainAllValuableFieldsWhenCalledOnMinimalObject() throws JsonProcessingException {
+ //given
+ final KeyRotationPolicyModel model = new KeyRotationPolicyModel();
+ model.setId(URI.create(POLICY_URI_STRING));
+ model.setLifetimeActions(List.of(ROTATE_ACTION));
+ model.setAttributes(policyAttributes(null));
+ final String expected = readResourceAsStringRemoveWhitespace(MINIMUM_JSON);
+
+ //when
+ final String actual = objectMapper.writer().writeValueAsString(model);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @Test
+ void testJsonDeserializationShouldRestoreAllValuableFieldsWhenCalledWithFullyPopulatedJson() throws IOException {
+ //given
+
+ //when
+ final KeyRotationPolicyModel actual = loadResourceAsObject(FULL_JSON);
+
+ //then
+ Assertions.assertEquals(POLICY_URI_STRING, actual.getId().toString());
+ Assertions.assertEquals(EXPIRY_PERIOD_4M, actual.getAttributes().getExpiryTime());
+ Assertions.assertEquals(CREATED_ON, actual.getAttributes().getCreatedOn());
+ Assertions.assertEquals(UPDATED_ON, actual.getAttributes().getUpdatedOn());
+ Assertions.assertIterableEquals(List.of(ROTATE_ACTION, NOTIFY_ACTION), actual.getLifetimeActions());
+ }
+
+
+ @Test
+ void testJsonDeserializationShouldRestoreAllValuableFieldsWhenCalledWithMinimalJson() throws IOException {
+ //given
+
+ //when
+ final KeyRotationPolicyModel actual = loadResourceAsObject(MINIMUM_JSON);
+
+ //then
+ Assertions.assertEquals(POLICY_URI_STRING, actual.getId().toString());
+ Assertions.assertNull(actual.getAttributes().getExpiryTime());
+ Assertions.assertEquals(CREATED_ON, actual.getAttributes().getCreatedOn());
+ Assertions.assertEquals(UPDATED_ON, actual.getAttributes().getUpdatedOn());
+ Assertions.assertIterableEquals(List.of(ROTATE_ACTION), actual.getLifetimeActions());
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidProvider")
+ void testValidateShouldReportViolationsWhenCalledWithInvalidData(
+ final String resource, final String expectedPath) throws IOException {
+ //given
+ final KeyRotationPolicyModel underTest = loadResourceAsObject(resource);
+
+ //when
+ final Set> violations = validator.validate(underTest);
+
+ //then
+ Assertions.assertEquals(1, violations.size());
+ Assertions.assertEquals(expectedPath, violations.iterator().next().getPropertyPath().toString());
+ }
+
+ private KeyRotationPolicyAttributes policyAttributes(
+ final Period expiryTime) {
+ final KeyRotationPolicyAttributes attributes = new KeyRotationPolicyAttributes();
+ attributes.setExpiryTime(expiryTime);
+ attributes.setCreatedOn(CREATED_ON);
+ attributes.setUpdatedOn(UPDATED_ON);
+ return attributes;
+ }
+
+ private KeyRotationPolicyModel loadResourceAsObject(final String resource) throws IOException {
+ final String json = ResourceUtils.loadResourceAsString(resource);
+ return objectMapper.reader().readValue(json, KeyRotationPolicyModel.class);
+ }
+
+ private String readResourceAsStringRemoveWhitespace(final String resource) {
+ final String json = ResourceUtils.loadResourceAsString(resource);
+ return Objects.requireNonNull(json).replaceAll("[ \\n]+", "");
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionTypeTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionTypeTest.java
new file mode 100644
index 00000000..f2378e80
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/constants/LifetimeActionTypeTest.java
@@ -0,0 +1,37 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key.constants;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+class LifetimeActionTypeTest {
+
+ public static Stream valueProvider() {
+ final List list = new ArrayList<>();
+ list.add(null);
+ list.addAll(Arrays.asList(LifetimeActionType.values()));
+ return list.stream()
+ .map(value -> Arguments.of(
+ Optional.ofNullable(value).map(LifetimeActionType::getValue).orElse("unknown"),
+ value));
+ }
+
+ @ParameterizedTest
+ @MethodSource("valueProvider")
+ void testForValueShouldReturnEnumWhenValueStringMatches(final String input, final LifetimeActionType expected) {
+ //given
+
+ //when
+ final LifetimeActionType actual = LifetimeActionType.forValue(input);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidatorTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidatorTest.java
new file mode 100644
index 00000000..a66b5bc0
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/model/v7_3/key/validator/ExpiryPeriodValidatorTest.java
@@ -0,0 +1,51 @@
+package com.github.nagyesta.lowkeyvault.model.v7_3.key.validator;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import javax.validation.ConstraintValidatorContext;
+import java.time.Period;
+import java.util.stream.Stream;
+
+import static org.mockito.Mockito.mock;
+
+class ExpiryPeriodValidatorTest {
+
+ @ExpiryPeriod
+ private Period dummy;
+
+ public static Stream isValidProvider() {
+ return Stream.builder()
+ .add(Arguments.of(Period.parse("-P1D"), false))
+ .add(Arguments.of(Period.parse("P1D"), false))
+ .add(Arguments.of(Period.parse("P5D"), false))
+ .add(Arguments.of(Period.parse("P10D"), false))
+ .add(Arguments.of(Period.parse("P26D"), false))
+ .add(Arguments.of(Period.parse("P27D"), false))
+ .add(Arguments.of(Period.parse("P28D"), true))
+ .add(Arguments.of(Period.parse("P29D"), true))
+ .add(Arguments.of(Period.parse("P1M"), true))
+ .add(Arguments.of(Period.parse("P2M"), true))
+ .add(Arguments.of(Period.parse("P1Y2M"), true))
+ .add(Arguments.of(null, true))
+ .build();
+ }
+
+ @ParameterizedTest
+ @MethodSource("isValidProvider")
+ void testIsValidShouldReturnTrueOnlyWhenCalledWithValidData(final Period input, final boolean expected) throws NoSuchFieldException {
+ //given
+ final ExpiryPeriodValidator underTest = new ExpiryPeriodValidator();
+ final ExpiryPeriod annotation = this.getClass().getDeclaredField("dummy").getAnnotation(ExpiryPeriod.class);
+
+ //when
+ underTest.initialize(annotation);
+ final boolean actual = underTest.isValid(input, mock(ConstraintValidatorContext.class));
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/KeyLifetimeActionTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/KeyLifetimeActionTest.java
new file mode 100644
index 00000000..6f6d1e0a
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/KeyLifetimeActionTest.java
@@ -0,0 +1,49 @@
+package com.github.nagyesta.lowkeyvault.service.key;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.impl.KeyLifetimeActionTrigger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.time.Period;
+import java.util.stream.Stream;
+
+class KeyLifetimeActionTest {
+
+ public static Stream invalidProvider() {
+ return Stream.builder()
+ .add(Arguments.of(null, null))
+ .add(Arguments.of(null, new KeyLifetimeActionTrigger(Period.ZERO, LifetimeActionTriggerType.TIME_BEFORE_EXPIRY)))
+ .add(Arguments.of(LifetimeActionType.NOTIFY, null))
+ .build();
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidProvider")
+ void testConstructorShouldThrowExceptionWhenCalledWithNulls(final LifetimeActionType type, final LifetimeActionTrigger trigger) {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new KeyLifetimeAction(type, trigger));
+
+ //then + exception
+ }
+
+ @Test
+ void testConstructorShouldSetValuesWhenCalledWithValidInput() {
+ //given
+ final LifetimeActionType type = LifetimeActionType.NOTIFY;
+ final LifetimeActionTrigger trigger = new KeyLifetimeActionTrigger(Period.ZERO, LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+
+ //when
+ final KeyLifetimeAction actual = new KeyLifetimeAction(type, trigger);
+
+ //then
+ Assertions.assertEquals(trigger, actual.getTrigger());
+ Assertions.assertEquals(type, actual.getActionType());
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerTypeTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerTypeTest.java
new file mode 100644
index 00000000..028d74ae
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/constants/LifetimeActionTriggerTypeTest.java
@@ -0,0 +1,132 @@
+package com.github.nagyesta.lowkeyvault.service.key.constants;
+
+import com.github.nagyesta.lowkeyvault.TestConstants;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType.TIME_AFTER_CREATE;
+import static com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType.TIME_BEFORE_EXPIRY;
+
+class LifetimeActionTriggerTypeTest {
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public static Stream validationDataProvider() {
+ return Stream.builder()
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, null, 30, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.TIME_10_MINUTES_AGO, 30, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 30, 5, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 30, 6, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 30, 8, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 1, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 27, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 28, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, null, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.NOW, 28, null, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, 30, 7, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.TIME_10_MINUTES_AGO, 30, 23, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, 30, 25, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, 30, 24, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, 30, 22, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 1, 20, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 27, 20, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 28, 20, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 28, 21, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 28, 22, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, null, 21, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.NOW, 28, null, false))
+ .build();
+ }
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public static Stream shouldTriggerValidDataProvider() {
+ return Stream.builder()
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 50, 44, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 50, 43, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 50, 42, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 50, 41, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 28, 22, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 28, 21, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 28, 20, 7, false))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 10, 3, 7, true))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, 10, 3, 6, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, 50, 44, 43, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, 50, 43, 43, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, 50, 42, 43, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, 50, 41, 43, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, 28, 22, 21, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, 28, 21, 21, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, 28, 20, 21, false))
+ .add(Arguments.of(TIME_AFTER_CREATE, 10, 3, 3, true))
+ .add(Arguments.of(TIME_AFTER_CREATE, 10, 3, 4, false))
+ .build();
+ }
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public static Stream shouldTriggerInvalidDataProvider() {
+ return Stream.builder()
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, null, null, null))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.TIME_10_MINUTES_AGO, null, 0))
+ .add(Arguments.of(TIME_BEFORE_EXPIRY, TestConstants.TIME_10_MINUTES_AGO, TestConstants.NOW, null))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, null, null))
+ .add(Arguments.of(TIME_AFTER_CREATE, TestConstants.TIME_10_MINUTES_AGO, null, null))
+ .add(Arguments.of(TIME_AFTER_CREATE, null, TestConstants.NOW, 0))
+ .build();
+ }
+
+ @ParameterizedTest
+ @MethodSource("validationDataProvider")
+ void testValidateShouldThrowExceptionWhenInputIsInvalid(
+ final LifetimeActionTriggerType underTest, final OffsetDateTime expires,
+ final Integer expiryDays, final Integer triggerDays, final boolean valid) {
+ //given
+ final Period expiryPeriod = Optional.ofNullable(expiryDays).map(Period::ofDays).orElse(null);
+ final Period triggerPeriod = Optional.ofNullable(triggerDays).map(Period::ofDays).orElse(null);
+
+ //when
+ if (valid) {
+ Assertions.assertDoesNotThrow(() -> underTest.validate(expires, expiryPeriod, triggerPeriod));
+ } else {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.validate(expires, expiryPeriod, triggerPeriod));
+ }
+
+ //then + exception if not valid
+ }
+
+ @ParameterizedTest
+ @MethodSource("shouldTriggerValidDataProvider")
+ void testShouldTriggerShouldReturnTrueWhenCalledInsideTheTriggerPeriod(
+ final LifetimeActionTriggerType underTest, final int expiryAfterCreateDays,
+ final int createOffsetDays, final int triggerDays, final boolean expected) {
+ //given
+ final OffsetDateTime createTime = TestConstants.NOW.minusDays(createOffsetDays);
+ final OffsetDateTime expiryTime = createTime.plusDays(expiryAfterCreateDays);
+ final Period triggerPeriod = Period.ofDays(triggerDays);
+
+ //when
+ final boolean actual = underTest.shouldTrigger(createTime, expiryTime, triggerPeriod);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+
+ @ParameterizedTest
+ @MethodSource("shouldTriggerInvalidDataProvider")
+ void testShouldTriggerShouldThrowExceptionWhenCalledWithInvalidData(
+ final LifetimeActionTriggerType underTest, final OffsetDateTime createTime,
+ final OffsetDateTime expiryTime, final Integer triggerDays) {
+ //given
+ final Period triggerPeriod = Optional.ofNullable(triggerDays).map(Period::ofDays).orElse(null);
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.shouldTrigger(createTime, expiryTime, triggerPeriod));
+
+ //then + exception
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityIdTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityIdTest.java
new file mode 100644
index 00000000..bd35e711
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/id/KeyEntityIdTest.java
@@ -0,0 +1,22 @@
+package com.github.nagyesta.lowkeyvault.service.key.id;
+
+import com.github.nagyesta.lowkeyvault.TestConstantsKeys;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+
+class KeyEntityIdTest {
+
+ @Test
+ void testAsRotationPolicyUriShouldReturnRotationPolicyUriWhenCalled() {
+ //given
+ final KeyEntityId underTest = TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+
+ //when
+ final URI actual = underTest.asRotationPolicyUri();
+
+ //then
+ Assertions.assertEquals(underTest.asUri("rotationpolicy"), actual);
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntityTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntityTest.java
index 664e1166..303b9274 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntityTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/AesKeyVaultKeyEntityTest.java
@@ -244,4 +244,21 @@ void testVerifyShouldThrowExceptionWhenCalled() {
//then + exception
}
+
+ @Test
+ void testKeyCreationInputShouldReturnOriginalParameters() {
+ //given
+ final VaultFake vaultFake = new VaultFakeImpl(HTTPS_LOWKEY_VAULT);
+ final int keySize = KeyType.OCT.getValidKeyParameters(Integer.class).first();
+ final AesKeyVaultKeyEntity underTest = new AesKeyVaultKeyEntity(
+ VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, keySize, false);
+
+ //when
+ final KeyCreationInput> actual = underTest.keyCreationInput();
+
+ //then
+ Assertions.assertInstanceOf(OctKeyCreationInput.class, actual);
+ final OctKeyCreationInput value = (OctKeyCreationInput) actual;
+ Assertions.assertEquals(keySize, value.getKeyParameter());
+ }
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java
index 9a8e073d..7f7ddc72 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/EcKeyVaultKeyEntityTest.java
@@ -265,6 +265,23 @@ void testVerifyShouldThrowExceptionWhenWhenDigestSizeIsNotCompatible(final byte[
//then + exception
}
+ @Test
+ void testKeyCreationInputShouldReturnOriginalParameters() {
+ //given
+ final VaultFake vaultFake = new VaultFakeImpl(HTTPS_LOWKEY_VAULT);
+ final KeyCurveName keyCurveName = KeyCurveName.P_384;
+ final EcKeyVaultKeyEntity underTest = new EcKeyVaultKeyEntity(
+ VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, keyCurveName, false);
+
+ //when
+ final KeyCreationInput> actual = underTest.keyCreationInput();
+
+ //then
+ Assertions.assertInstanceOf(EcKeyCreationInput.class, actual);
+ final EcKeyCreationInput value = (EcKeyCreationInput) actual;
+ Assertions.assertEquals(keyCurveName, value.getKeyParameter());
+ }
+
private byte[] hash(final byte[] text, final SignatureAlgorithm algorithm) {
try {
final MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHMS.get(algorithm));
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTriggerTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTriggerTest.java
new file mode 100644
index 00000000..d7dbe9cf
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyLifetimeActionTriggerTest.java
@@ -0,0 +1,71 @@
+package com.github.nagyesta.lowkeyvault.service.key.impl;
+
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.stream.Stream;
+
+import static com.github.nagyesta.lowkeyvault.TestConstants.NOW;
+import static com.github.nagyesta.lowkeyvault.TestConstants.TIME_IN_10_MINUTES;
+import static com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType.*;
+
+class KeyLifetimeActionTriggerTest {
+
+ public static final OffsetDateTime A_WEEK_AGO = NOW.minusDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ public static final OffsetDateTime IN_A_WEEK = NOW.plusDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ public static final OffsetDateTime IN_A_WEEK_AND_10_MINUTES = TIME_IN_10_MINUTES.plusDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ public static final Period PERIOD_A_WEEK = Period.ofDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ public static final Period PERIOD_28_DAYS = Period.ofDays(MINIMUM_EXPIRY_PERIOD_IN_DAYS);
+
+ public static Stream invalidProvider() {
+ return Stream.builder()
+ .add(Arguments.of(null, null))
+ .add(Arguments.of(null, LifetimeActionTriggerType.TIME_BEFORE_EXPIRY))
+ .add(Arguments.of(Period.ZERO, null))
+ .build();
+ }
+
+
+ public static Stream validProvider() {
+ return Stream.builder()
+ .add(Arguments.of(PERIOD_A_WEEK, TIME_AFTER_CREATE, A_WEEK_AGO, null, true))
+ .add(Arguments.of(PERIOD_28_DAYS, TIME_AFTER_CREATE, A_WEEK_AGO, null, false))
+ .add(Arguments.of(PERIOD_28_DAYS, TIME_AFTER_CREATE, A_WEEK_AGO, NOW, false))
+ .add(Arguments.of(PERIOD_A_WEEK, TIME_BEFORE_EXPIRY, NOW, IN_A_WEEK, true))
+ .add(Arguments.of(PERIOD_A_WEEK, TIME_BEFORE_EXPIRY, NOW, IN_A_WEEK_AND_10_MINUTES, false))
+ .add(Arguments.of(PERIOD_28_DAYS, TIME_BEFORE_EXPIRY, NOW.minusYears(1), NOW.plusYears(1), false))
+ .add(Arguments.of(PERIOD_28_DAYS, TIME_BEFORE_EXPIRY, NOW.minusYears(1), NOW.plusDays(1), true))
+ .build();
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidProvider")
+ void testConstructorShouldThrowExceptionWhenCalledWithNulls(final Period period, final LifetimeActionTriggerType type) {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new KeyLifetimeActionTrigger(period, type));
+
+ //then + exception
+ }
+
+ @ParameterizedTest
+ @MethodSource("validProvider")
+ void shouldTrigger(final Period period, final LifetimeActionTriggerType type,
+ final OffsetDateTime created, final OffsetDateTime expiry,
+ final boolean expected) {
+ //given
+ final KeyLifetimeActionTrigger underTest = new KeyLifetimeActionTrigger(period, type);
+
+ //when
+ final boolean actual = underTest.shouldTrigger(created, expiry);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicyTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicyTest.java
new file mode 100644
index 00000000..a8b38a42
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyRotationPolicyTest.java
@@ -0,0 +1,305 @@
+package com.github.nagyesta.lowkeyvault.service.key.impl;
+
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
+import com.github.nagyesta.lowkeyvault.service.key.KeyLifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.LifetimeAction;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
+import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static com.github.nagyesta.lowkeyvault.TestConstants.*;
+import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.UNVERSIONED_KEY_ENTITY_ID_1;
+
+class KeyRotationPolicyTest {
+
+ private static final String DAYS_42 = "P42D";
+ private static final String DAYS_100 = "P100D";
+ private static final String DAYS_7 = "P7D";
+ private static final KeyLifetimeActionTrigger TRIGGER_7_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeActionTrigger(Period.parse(DAYS_7), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+ private static final KeyLifetimeActionTrigger TRIGGER_42_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeActionTrigger(Period.parse(DAYS_42), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY);
+ private static final KeyLifetimeAction NOTIFY_7_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeAction(LifetimeActionType.NOTIFY, TRIGGER_7_DAYS_BEFORE_EXPIRY);
+ private static final KeyLifetimeAction NOTIFY_42_DAYS_BEFORE_EXPIRY =
+ new KeyLifetimeAction(LifetimeActionType.NOTIFY, TRIGGER_42_DAYS_BEFORE_EXPIRY);
+
+ private static final KeyLifetimeActionTrigger TRIGGER_42_DAYS_AFTER_CREATE =
+ new KeyLifetimeActionTrigger(Period.parse(DAYS_42), LifetimeActionTriggerType.TIME_AFTER_CREATE);
+ private static final KeyLifetimeAction ROTATE_42_DAYS_AFTER_CREATE =
+ new KeyLifetimeAction(LifetimeActionType.ROTATE, TRIGGER_42_DAYS_AFTER_CREATE);
+ private static final int OFFSET_SECONDS_10_MINUTES = 600;
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public static Stream invalidDataProvider() {
+ return Stream.builder()
+ .add(Arguments.of(OffsetDateTime.now().plusDays(10), Period.ofDays(120),
+ Period.ofDays(30), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY,
+ Period.ofDays(115), LifetimeActionTriggerType.TIME_AFTER_CREATE, false))
+ .add(Arguments.of(OffsetDateTime.now().plusDays(10), Period.ofDays(20),
+ Period.ofDays(7), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY,
+ Period.ofDays(13), LifetimeActionTriggerType.TIME_AFTER_CREATE, false))
+ .add(Arguments.of(OffsetDateTime.now().plusDays(10), Period.ofDays(120),
+ Period.ofDays(7), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY,
+ Period.ofDays(13), LifetimeActionTriggerType.TIME_BEFORE_EXPIRY, true))
+ .add(Arguments.of(OffsetDateTime.now().plusDays(10), Period.ofDays(120),
+ Period.ofDays(30), LifetimeActionTriggerType.TIME_AFTER_CREATE,
+ Period.ofDays(100), LifetimeActionTriggerType.TIME_AFTER_CREATE, false))
+ .build();
+ }
+
+ public static Stream invalidProvider() {
+ return Stream.builder()
+ .add(Arguments.of(null, null, null))
+ .add(Arguments.of(UNVERSIONED_KEY_ENTITY_ID_1, null, null))
+ .add(Arguments.of(null, Period.ZERO, null))
+ .add(Arguments.of(null, null, Map.of()))
+ .add(Arguments.of(null, Period.ZERO, Map.of()))
+ .add(Arguments.of(UNVERSIONED_KEY_ENTITY_ID_1, null, Map.of()))
+ .add(Arguments.of(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, null))
+ .build();
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidProvider")
+ void testConstructorShouldThrowExceptionWhenCalledWithNull(final KeyEntityId keyEntityId,
+ final Period period,
+ final Map actions) {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> new KeyRotationPolicy(keyEntityId, period, actions));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetCreatedOnShouldUpdateValueWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ final OffsetDateTime original = underTest.getCreatedOn();
+
+ //when
+ underTest.setCreatedOn(TIME_10_MINUTES_AGO);
+ final OffsetDateTime actual = underTest.getCreatedOn();
+
+ //then
+ Assertions.assertNotEquals(original, actual);
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, actual);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testSetCreatedOnShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setCreatedOn(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetUpdatedOnShouldUpdateValueWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ final OffsetDateTime original = underTest.getUpdatedOn();
+
+ //when
+ underTest.setUpdatedOn(TIME_10_MINUTES_AGO);
+ final OffsetDateTime actual = underTest.getUpdatedOn();
+
+ //then
+ Assertions.assertNotEquals(original, actual);
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, actual);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testSetUpdatedOnShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setUpdatedOn(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetExpiryTimeShouldUpdateValueWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ final Period original = underTest.getExpiryTime();
+
+ //when
+ underTest.setExpiryTime(Period.parse(DAYS_42));
+ final Period actual = underTest.getExpiryTime();
+
+ //then
+ Assertions.assertEquals(Period.ZERO, original);
+ Assertions.assertEquals(Period.parse(DAYS_42), actual);
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testSetExpiryTimeShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setExpiryTime(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetExpiryTimeShouldUpdateUpdatedOnWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ underTest.setUpdatedOn(TIME_10_MINUTES_AGO);
+
+ //when
+ underTest.setExpiryTime(Period.parse(DAYS_42));
+ final OffsetDateTime actual = underTest.getUpdatedOn();
+
+ //then
+ Assertions.assertTrue(actual.isAfter(TIME_10_MINUTES_AGO));
+ }
+
+ @Test
+ void testSetLifetimeActionsShouldUpdateValueWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ final Map original = underTest.getLifetimeActions();
+ final Map map = Map.of(LifetimeActionType.NOTIFY, NOTIFY_42_DAYS_BEFORE_EXPIRY);
+
+ //when
+ underTest.setLifetimeActions(map);
+ final Map actual = underTest.getLifetimeActions();
+
+ //then
+ Assertions.assertTrue(original.isEmpty());
+ Assertions.assertIterableEquals(map.entrySet(), actual.entrySet());
+ }
+
+ @Test
+ void testSetLifetimeActionsShouldUpdateValueWhenNotifyIsReplacedWithAnotherNotify() {
+ //given
+ final Map map = Map.of(LifetimeActionType.NOTIFY, NOTIFY_42_DAYS_BEFORE_EXPIRY);
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.parse(DAYS_100), map);
+ final Map original = underTest.getLifetimeActions();
+ final Map newValue = Map.of(
+ LifetimeActionType.NOTIFY, NOTIFY_7_DAYS_BEFORE_EXPIRY,
+ LifetimeActionType.ROTATE, ROTATE_42_DAYS_AFTER_CREATE);
+
+ //when
+ underTest.setLifetimeActions(newValue);
+ final Map actual = underTest.getLifetimeActions();
+
+ //then
+ Assertions.assertIterableEquals(map.entrySet(), original.entrySet());
+ Assertions.assertIterableEquals(newValue.entrySet(), actual.entrySet());
+ }
+
+ @Test
+ void testSetLifetimeActionsShouldThrowExceptionWhenNotifyIsBeingRemoved() {
+ //given
+ final Map map = Map.of(LifetimeActionType.NOTIFY, NOTIFY_42_DAYS_BEFORE_EXPIRY);
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, map);
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setLifetimeActions(Map.of()));
+
+ //then + exception
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testSetLifetimeActionsShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setLifetimeActions(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetLifetimeActionsShouldUpdateUpdatedOnWhenCalledWithValidInput() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ underTest.setUpdatedOn(TIME_10_MINUTES_AGO);
+
+ //when
+ underTest.setLifetimeActions(Map.of());
+ final OffsetDateTime actual = underTest.getUpdatedOn();
+
+ //then
+ Assertions.assertTrue(actual.isAfter(TIME_10_MINUTES_AGO));
+ }
+
+ @Test
+ void testTimeShiftShouldAdjustCreatedOnAndUpdatedOnWhenCalledWithValidData() {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+ underTest.setCreatedOn(NOW);
+ underTest.setUpdatedOn(TIME_IN_10_MINUTES);
+
+ //when
+ underTest.timeShift(OFFSET_SECONDS_10_MINUTES);
+
+ //then
+ Assertions.assertEquals(TIME_10_MINUTES_AGO, underTest.getCreatedOn());
+ Assertions.assertEquals(NOW, underTest.getUpdatedOn());
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-42, -2, -1, 0})
+ void testTimeShiftShouldThrowExceptionWhenCalledWithInvalidValue(final int value) {
+ //given
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, Period.ZERO, Map.of());
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.timeShift(value));
+
+ //then + exception
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidDataProvider")
+ void testValidateShouldThrowExceptionWhenDataIsInvalid(
+ final OffsetDateTime expiryTime, final Period expiryPeriod,
+ final Period notifyPeriod, final LifetimeActionTriggerType notifyTriggerType,
+ final Period rotatePeriod, final LifetimeActionTriggerType rotateTriggerType,
+ final boolean valid) {
+ //given
+ final KeyLifetimeActionTrigger notifyTrigger = new KeyLifetimeActionTrigger(notifyPeriod, notifyTriggerType);
+ final KeyLifetimeAction notify = new KeyLifetimeAction(LifetimeActionType.NOTIFY, notifyTrigger);
+ final KeyLifetimeActionTrigger rotateTrigger = new KeyLifetimeActionTrigger(rotatePeriod, rotateTriggerType);
+ final KeyLifetimeAction rotate = new KeyLifetimeAction(LifetimeActionType.ROTATE, rotateTrigger);
+ final KeyRotationPolicy underTest = new KeyRotationPolicy(UNVERSIONED_KEY_ENTITY_ID_1, expiryPeriod,
+ Map.of(notify.getActionType(), notify, rotate.getActionType(), rotate));
+
+ //when
+ if (valid) {
+ Assertions.assertDoesNotThrow(() -> underTest.validate(expiryTime));
+ } else {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.validate(expiryTime));
+ }
+
+ //then + exception
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImplTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImplTest.java
index fd191fce..4cfe9a78 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImplTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultFakeImplTest.java
@@ -4,10 +4,14 @@
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyCurveName;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyOperation;
import com.github.nagyesta.lowkeyvault.model.v7_2.key.constants.KeyType;
+import com.github.nagyesta.lowkeyvault.model.v7_3.key.constants.LifetimeActionType;
import com.github.nagyesta.lowkeyvault.service.exception.AlreadyExistsException;
import com.github.nagyesta.lowkeyvault.service.exception.NotFoundException;
+import com.github.nagyesta.lowkeyvault.service.key.KeyLifetimeAction;
import com.github.nagyesta.lowkeyvault.service.key.KeyVaultFake;
import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyKeyVaultKeyEntity;
+import com.github.nagyesta.lowkeyvault.service.key.ReadOnlyRotationPolicy;
+import com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType;
import com.github.nagyesta.lowkeyvault.service.key.id.KeyEntityId;
import com.github.nagyesta.lowkeyvault.service.key.id.VersionedKeyEntityId;
import com.github.nagyesta.lowkeyvault.service.vault.VaultFake;
@@ -22,10 +26,8 @@
import org.junit.jupiter.params.provider.ValueSource;
import java.time.OffsetDateTime;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.List;
+import java.time.Period;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -33,6 +35,8 @@
import static com.github.nagyesta.lowkeyvault.TestConstants.*;
import static com.github.nagyesta.lowkeyvault.TestConstantsKeys.*;
import static com.github.nagyesta.lowkeyvault.TestConstantsUri.HTTPS_LOCALHOST;
+import static com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType.MINIMUM_EXPIRY_PERIOD_IN_DAYS;
+import static com.github.nagyesta.lowkeyvault.service.key.constants.LifetimeActionTriggerType.MINIMUM_THRESHOLD_BEFORE_EXPIRY;
import static org.mockito.Mockito.mock;
class KeyVaultFakeImplTest {
@@ -605,7 +609,6 @@ void testRecoverShouldThrowExceptionWhenCalledWithNullKey() {
}
-
@Test
void testPurgeShouldThrowExceptionWhenCalledWithMissingDeletedKey() {
//given
@@ -648,7 +651,6 @@ void testPurgeShouldThrowExceptionWhenCalledWithNullKey() {
//then + exception
}
- @SuppressWarnings("checkstyle:MagicNumber")
@ParameterizedTest
@ValueSource(ints = {-42, -10, -5, -3, -2, -1, 0})
void testTimeShiftShouldThrowExceptionWhenCalledWithNegativeOrZero(final int value) {
@@ -670,6 +672,15 @@ void testTimeShiftShouldReduceTimeStampsWhenCalledOnActiveEntityWithPositiveValu
final ReadOnlyKeyVaultKeyEntity before = underTest.getEntities().getReadOnlyEntity(keyEntityId);
final OffsetDateTime createdOriginal = before.getCreated();
final OffsetDateTime updatedOriginal = before.getUpdated();
+ final KeyLifetimeActionTrigger trigger = new KeyLifetimeActionTrigger(
+ Period.ofDays(MINIMUM_EXPIRY_PERIOD_IN_DAYS),
+ LifetimeActionTriggerType.TIME_AFTER_CREATE);
+ final Period expiryTime = Period.ofDays(MINIMUM_EXPIRY_PERIOD_IN_DAYS + MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ underTest.setRotationPolicy(new KeyRotationPolicy(keyEntityId, expiryTime,
+ Map.of(LifetimeActionType.ROTATE, new KeyLifetimeAction(LifetimeActionType.ROTATE, trigger))));
+ final ReadOnlyRotationPolicy beforePolicy = underTest.rotationPolicy(keyEntityId);
+ final OffsetDateTime createdPolicyOriginal = beforePolicy.getCreatedOn();
+ final OffsetDateTime updatedPolicyOriginal = beforePolicy.getUpdatedOn();
//when
underTest.timeShift(NUMBER_OF_SECONDS_IN_10_MINUTES);
@@ -680,6 +691,9 @@ void testTimeShiftShouldReduceTimeStampsWhenCalledOnActiveEntityWithPositiveValu
Assertions.assertEquals(updatedOriginal.minusSeconds(NUMBER_OF_SECONDS_IN_10_MINUTES), after.getUpdated());
Assertions.assertEquals(TIME_10_MINUTES_AGO, after.getNotBefore().orElse(null));
Assertions.assertEquals(NOW, after.getExpiry().orElse(null));
+ final ReadOnlyRotationPolicy afterPolicy = underTest.rotationPolicy(keyEntityId);
+ Assertions.assertEquals(createdPolicyOriginal.minusSeconds(NUMBER_OF_SECONDS_IN_10_MINUTES), afterPolicy.getCreatedOn());
+ Assertions.assertEquals(updatedPolicyOriginal.minusSeconds(NUMBER_OF_SECONDS_IN_10_MINUTES), afterPolicy.getUpdatedOn());
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
@@ -709,6 +723,119 @@ void testTimeShiftShouldReduceTimeStampsWhenCalledOnDeletedEntityWithPositiveVal
Assertions.assertEquals(NOW, after.getExpiry().orElse(null));
}
+ @Test
+ void testSetRotationPolicyShouldKeepCreatedWhenCalledASecondTime() {
+ //given
+ final KeyVaultFake underTest = createUnderTest();
+ final VersionedKeyEntityId keyEntityId = underTest.createEcKeyVersion(KEY_NAME_1, EC_KEY_CREATION_INPUT);
+ underTest.setExpiry(keyEntityId, NOW, TIME_IN_10_MINUTES);
+ final KeyLifetimeActionTrigger rotateOriginal = new KeyLifetimeActionTrigger(
+ Period.ofDays(MINIMUM_THRESHOLD_BEFORE_EXPIRY),
+ LifetimeActionTriggerType.TIME_AFTER_CREATE);
+ final KeyLifetimeActionTrigger rotateSecond = new KeyLifetimeActionTrigger(
+ Period.ofDays(MINIMUM_EXPIRY_PERIOD_IN_DAYS),
+ LifetimeActionTriggerType.TIME_AFTER_CREATE);
+ final Period expiryTime = Period.ofDays(MINIMUM_EXPIRY_PERIOD_IN_DAYS + MINIMUM_THRESHOLD_BEFORE_EXPIRY);
+ final KeyRotationPolicy rotationPolicyOriginal = new KeyRotationPolicy(keyEntityId, expiryTime,
+ Map.of(LifetimeActionType.ROTATE, new KeyLifetimeAction(LifetimeActionType.ROTATE, rotateOriginal)));
+ final KeyRotationPolicy rotationPolicySecond = new KeyRotationPolicy(keyEntityId, expiryTime,
+ Map.of(LifetimeActionType.ROTATE, new KeyLifetimeAction(LifetimeActionType.ROTATE, rotateSecond)));
+ underTest.setRotationPolicy(rotationPolicyOriginal);
+ final ReadOnlyRotationPolicy beforePolicy = underTest.rotationPolicy(keyEntityId);
+ final OffsetDateTime createdPolicyOriginal = beforePolicy.getCreatedOn();
+ final OffsetDateTime updatedPolicyOriginal = beforePolicy.getUpdatedOn();
+
+ //when
+ underTest.setRotationPolicy(rotationPolicySecond);
+
+ //then
+ final ReadOnlyRotationPolicy afterPolicy = underTest.rotationPolicy(keyEntityId);
+ Assertions.assertEquals(createdPolicyOriginal, afterPolicy.getCreatedOn());
+ Assertions.assertTrue(updatedPolicyOriginal.isBefore(afterPolicy.getUpdatedOn()));
+ }
+
+ @Test
+ void testRotationPolicyShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyVaultFake underTest = createUnderTest();
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.rotationPolicy(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testSetRotationPolicyShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyVaultFake underTest = createUnderTest();
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.setRotationPolicy(null));
+
+ //then + exception
+ }
+
+ @Test
+ void testRotateKeyShouldCreateNewKeyVersionKeepingTagsAndOperationsWhenCalledWithExistingKey() {
+ //given
+ final KeyCurveName keyParameter = KeyCurveName.P_384;
+ final Map tags = Map.of(KEY_1, VALUE_1);
+ final List operations = List.of(KeyOperation.ENCRYPT);
+
+ final KeyVaultFake underTest = createUnderTest();
+ final VersionedKeyEntityId keyEntityId = underTest
+ .createKeyVersion(KEY_NAME_1, new EcKeyCreationInput(KeyType.EC, keyParameter));
+ underTest.setKeyOperations(keyEntityId, operations);
+ underTest.addTags(keyEntityId, tags);
+ final VersionedKeyEntityId latestBeforeRotate = underTest.getEntities().getLatestVersionOfEntity(keyEntityId);
+ underTest.setExpiry(keyEntityId, null, TIME_IN_10_MINUTES);
+ underTest.setRotationPolicy(new DefaultKeyRotationPolicy(keyEntityId));
+
+ //when
+ underTest.rotateKey(keyEntityId);
+
+ //then
+ final VersionedKeyEntityId latestAfterRotate = underTest.getEntities().getLatestVersionOfEntity(keyEntityId);
+ Assertions.assertNotEquals(latestBeforeRotate, latestAfterRotate);
+ final ReadOnlyKeyVaultKeyEntity actual = underTest.getEntities().getReadOnlyEntity(latestAfterRotate);
+ Assertions.assertEquals(keyParameter, actual.keyCreationInput().getKeyParameter());
+ Assertions.assertEquals(actual.getCreated().plusYears(1), actual.getExpiry().orElse(null));
+ Assertions.assertIterableEquals(operations, actual.getOperations());
+ Assertions.assertIterableEquals(tags.entrySet(), actual.getTags().entrySet());
+ }
+
+ @Test
+ void testRotateKeyShouldCreateNewKeyVersionWhenNoRotationPolicyIsDefined() {
+ //given
+ final KeyCurveName keyParameter = KeyCurveName.P_384;
+
+ final KeyVaultFake underTest = createUnderTest();
+ final VersionedKeyEntityId keyEntityId = underTest
+ .createKeyVersion(KEY_NAME_1, new EcKeyCreationInput(KeyType.EC, keyParameter));
+ final VersionedKeyEntityId latestBeforeRotate = underTest.getEntities().getLatestVersionOfEntity(keyEntityId);
+
+ //when
+ underTest.rotateKey(keyEntityId);
+
+ //then
+ final VersionedKeyEntityId latestAfterRotate = underTest.getEntities().getLatestVersionOfEntity(keyEntityId);
+ Assertions.assertNotEquals(latestBeforeRotate, latestAfterRotate);
+ final ReadOnlyKeyVaultKeyEntity actual = underTest.getEntities().getReadOnlyEntity(latestAfterRotate);
+ Assertions.assertEquals(keyParameter, actual.keyCreationInput().getKeyParameter());
+ }
+
+ @Test
+ void testRotateKeyShouldThrowExceptionWhenCalledWithNull() {
+ //given
+ final KeyVaultFake underTest = createUnderTest();
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> underTest.rotateKey(null));
+
+ //then + exception
+ }
+
private KeyVaultFake createUnderTest() {
final KeyVaultFake underTest = new VaultFakeImpl(HTTPS_LOCALHOST,
RecoveryLevel.RECOVERABLE_AND_PURGEABLE,
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntityTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntityTest.java
index f20e974b..1e93340a 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntityTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/KeyVaultKeyEntityTest.java
@@ -116,7 +116,6 @@ void testCanPurgeShouldReturnFalseWhenRecoveryLevelIsNotPurgeableAndItemIsNotDel
Assertions.assertFalse(actual);
}
- @SuppressWarnings("checkstyle:MagicNumber")
@ParameterizedTest
@ValueSource(ints = {-42, -10, -5, -3, -2, -1, 0})
void testTimeShiftShouldThrowExceptionWhenCalledWithNegativeOrZero(final int value) {
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntityTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntityTest.java
index df0b6e79..ecbff34d 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntityTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/impl/RsaKeyVaultKeyEntityTest.java
@@ -245,4 +245,23 @@ void testVerifyShouldThrowExceptionWhenKeyIsNotEnabled() {
//then + exception
}
+
+ @Test
+ void testKeyCreationInputShouldReturnOriginalParameters() {
+ //given
+ final VaultFake vaultFake = new VaultFakeImpl(HTTPS_LOWKEY_VAULT);
+ final int keySize = EncryptionAlgorithm.RSA_OAEP_256.getMinKeySize();
+ final BigInteger publicExponent = new BigInteger("3");
+ final RsaKeyVaultKeyEntity underTest = new RsaKeyVaultKeyEntity(
+ VERSIONED_KEY_ENTITY_ID_1_VERSION_1, vaultFake, keySize, publicExponent, false);
+
+ //when
+ final KeyCreationInput> actual = underTest.keyCreationInput();
+
+ //then
+ Assertions.assertInstanceOf(RsaKeyCreationInput.class, actual);
+ final RsaKeyCreationInput value = (RsaKeyCreationInput) actual;
+ Assertions.assertEquals(keySize, value.getKeyParameter());
+ Assertions.assertEquals(publicExponent, value.getPublicExponent());
+ }
}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtilTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtilTest.java
new file mode 100644
index 00000000..b044102a
--- /dev/null
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/key/util/PeriodUtilTest.java
@@ -0,0 +1,67 @@
+package com.github.nagyesta.lowkeyvault.service.key.util;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.time.ZoneOffset;
+import java.util.stream.Stream;
+
+class PeriodUtilTest {
+
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public static Stream validProvider() {
+ return Stream.builder()
+ .add(Arguments.of("-P1D", -1))
+ .add(Arguments.of("P0D", 0))
+ .add(Arguments.of("P1D", 1))
+ .add(Arguments.of("P5D", 5))
+ .add(Arguments.of("P7D", 7))
+ .add(Arguments.of("P2M10D", 71))
+ .add(Arguments.of("P1Y1M1D", 397))
+ .build();
+ }
+
+ @Test
+ void testConstructorShouldThrowExceptionWhenCalled() throws NoSuchMethodException {
+ //given
+ final Constructor constructor = PeriodUtil.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+
+ //when
+ Assertions.assertThrows(InvocationTargetException.class, constructor::newInstance);
+
+ //then + exception
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ void testAsDaysShouldThrowExceptionWhenCalledWithNull() {
+ //given
+
+ //when
+ Assertions.assertThrows(IllegalArgumentException.class, () -> PeriodUtil.asDays(null));
+
+ //then + exception
+ }
+
+ @ParameterizedTest
+ @MethodSource("validProvider")
+ void testAsDaysShouldReturnNumberOfDaysWhenCalledWithValidPeriods(final String period, final int expected) {
+ //given
+ final OffsetDateTime time = OffsetDateTime.of(2022, 5, 10, 0, 0, 0, 0, ZoneOffset.UTC);
+ final Period parsed = Period.parse(period);
+
+ //when
+ final long actual = PeriodUtil.asDays(parsed, time);
+
+ //then
+ Assertions.assertEquals(expected, actual);
+ }
+}
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultFakeImplTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultFakeImplTest.java
index 7a72a7f7..acf53489 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultFakeImplTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultFakeImplTest.java
@@ -216,7 +216,6 @@ void testGetCreatedOnShouldBeInThePastWhenCalled() {
Assertions.assertTrue(actual.isBefore(OffsetDateTime.now()));
}
- @SuppressWarnings("checkstyle:MagicNumber")
@ParameterizedTest
@ValueSource(ints = {-42, -10, -5, -3, -2, -1, 0})
void testTimeShiftShouldThrowExceptionWhenCalledWithNegativeOrZero(final int value) {
diff --git a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultServiceImplTest.java b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultServiceImplTest.java
index 1cfe0ca6..9a869753 100644
--- a/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultServiceImplTest.java
+++ b/lowkey-vault-app/src/test/java/com/github/nagyesta/lowkeyvault/service/vault/impl/VaultServiceImplTest.java
@@ -268,7 +268,6 @@ void testListDeletedShouldNotReturnPurgedItemsWhenCalled(final Map
});
}
- @SuppressWarnings("checkstyle:MagicNumber")
@ParameterizedTest
@ValueSource(ints = {-42, -10, -5, -3, -2, -1, 0})
void testTimeShiftShouldThrowExceptionWhenCalledWithNegativeOrZero(final int value) {
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-empty-actions.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-empty-actions.json
new file mode 100644
index 00000000..0fb3a68a
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-empty-actions.json
@@ -0,0 +1,9 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [],
+ "attributes": {
+ "expiryTime": "P4M",
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-actions.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-actions.json
new file mode 100644
index 00000000..064d9a9d
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-actions.json
@@ -0,0 +1,8 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "attributes": {
+ "expiryTime": "P4M",
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-attributes.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-attributes.json
new file mode 100644
index 00000000..68328efb
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-attributes.json
@@ -0,0 +1,21 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeAfterCreate": "P90D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ },
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-expiry.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-expiry.json
new file mode 100644
index 00000000..8de7f79c
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-missing-expiry.json
@@ -0,0 +1,25 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeAfterCreate": "P90D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ },
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ }
+ ],
+ "attributes": {
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-action-type.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-action-type.json
new file mode 100644
index 00000000..3c73b163
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-action-type.json
@@ -0,0 +1,26 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeAfterCreate": "P90D"
+ },
+ "action": {
+ "type": null
+ }
+ },
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P4M",
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-trigger.json b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-trigger.json
new file mode 100644
index 00000000..e2971725
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/invalid-rotation-policy-null-trigger.json
@@ -0,0 +1,16 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": null,
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P4M",
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-full.json b/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-full.json
new file mode 100644
index 00000000..b8daf9d4
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-full.json
@@ -0,0 +1,26 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeAfterCreate": "P90D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ },
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P4M",
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-minimum.json b/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-minimum.json
new file mode 100644
index 00000000..262047aa
--- /dev/null
+++ b/lowkey-vault-app/src/test/resources/key/rotation/valid-rotation-policy-minimum.json
@@ -0,0 +1,17 @@
+{
+ "id": "https:/localhost:8443/keys/key-name/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeAfterCreate": "P90D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "created": 1482188947,
+ "updated": 1482188948
+ }
+}
diff --git a/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/TimeShiftContextTest.java b/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/TimeShiftContextTest.java
index 28a81c45..0daf68f3 100644
--- a/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/TimeShiftContextTest.java
+++ b/lowkey-vault-client/src/test/java/com/github/nagyesta/lowkeyvault/http/management/TimeShiftContextTest.java
@@ -7,7 +7,6 @@
class TimeShiftContextTest {
- @SuppressWarnings("checkstyle:MagicNumber")
@ParameterizedTest
@ValueSource(ints = {-42, -10, -5, -3, -2, -1, 0})
void testBuilderShouldThrowExceptionWhenCalledWithNegativeValue(final int value) {
diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/hook/MissionOutlineDefinition.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/hook/MissionOutlineDefinition.java
index e55d67cc..24894c5d 100644
--- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/hook/MissionOutlineDefinition.java
+++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/hook/MissionOutlineDefinition.java
@@ -48,7 +48,7 @@ protected Map> defineOutline() {
});
});
- Stream.of("CreateVault", "KeyImport", "KeyEncrypt", "KeySign", "RSA", "EC", "OCT").forEach(tag -> {
+ Stream.of("CreateVault", "KeyRotate", "KeyImport", "KeyEncrypt", "KeySign", "RSA", "EC", "OCT").forEach(tag -> {
final MissionHealthCheckMatcher matcher = matcher().dependencyWith(tag).extractor(extractor).build();
final MissionHealthCheckEvaluator tagPercentage = percentageBasedEvaluator(matcher)
.abortThreshold(ABORT_THRESHOLD)
diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CommonAssertions.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CommonAssertions.java
index f3b094dd..557731a7 100644
--- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CommonAssertions.java
+++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/CommonAssertions.java
@@ -61,7 +61,8 @@ protected String readResourceContent(final String resource) throws IOException {
try (InputStream stream = getClass().getResourceAsStream(resource);
InputStreamReader reader = new InputStreamReader(Objects.requireNonNull(stream));
BufferedReader bufferedReader = new BufferedReader(reader)) {
- return bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()));
+ return bufferedReader.lines().collect(Collectors.joining(""))
+ .replaceAll(" +", "");
}
}
}
diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java
index 303a4386..51ab51ed 100644
--- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java
+++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefs.java
@@ -443,6 +443,14 @@ public void theVaultIsCalledForBytesOfRandomData(final int count) {
context.setBackupBytes("random", bytes);
}
+ @When("the key named {name} is rotated")
+ public void theKeyIsRotated(final String name) {
+ final String oldId = context.getLastResult().getId();
+ final KeyVaultKey keyVaultKey = context.getClient(context.getKeyServiceVersion()).rotateKey(name);
+ assertTrue(!oldId.equals(keyVaultKey.getId()));
+ context.addFetchedKey(name, keyVaultKey);
+ }
+
private byte[] hash(final byte[] text, final String algorithm) {
try {
final MessageDigest md = MessageDigest.getInstance("SHA-" + algorithm.substring(2, 5));
diff --git a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefsAssertions.java b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefsAssertions.java
index 8a6a5551..fbb3f028 100644
--- a/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefsAssertions.java
+++ b/lowkey-vault-docker/src/test/java/com/github/nagyesta/lowkeyvault/steps/KeysStepDefsAssertions.java
@@ -173,7 +173,8 @@ public void theKeyNamedNameMatchesThePreviousBackup(final String name) {
@And("the unpacked backup of {name} matches the content of the classpath resource")
public void theKeyNamedNameMatchesTheResourceContent(final String name) throws IOException {
final byte[] bytes = context.getClient(context.getKeyServiceVersion()).backupKey(name);
- final String backup = context.getLowkeyVaultManagementClient().unpackBackup(bytes);
+ final String backup = context.getLowkeyVaultManagementClient().unpackBackup(bytes)
+ .replaceAll("[ \\n]+", "");
final String expected = readResourceContent("/json/backups/" + name + ".json");
assertEquals(expected, backup);
}
diff --git a/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature
new file mode 100644
index 00000000..3ab3e5ea
--- /dev/null
+++ b/lowkey-vault-docker/src/test/resources/com/github/nagyesta/lowkeyvault/keys/RotateKeys.feature
@@ -0,0 +1,92 @@
+Feature: Key rotation
+
+ @Key @KeyCreate @KeyRotate @RSA
+ Scenario Outline: RSA_ROTATE_01 Single versions of RSA keys can be created with the key client, then rotated and result observed
+ Given key API version is used
+ And a key client is created with the vault named keys-generic
+ And an RSA key named is prepared with bits size HSM
+ And the key is set to expire seconds after creation
+ And the key is set to be not usable until seconds after creation
+ And the key is set to use as tags
+ And the key has operations granted
+ And the key is set to be
+ And the RSA key is created
+ When the key named is rotated
+ Then the created key is using RSA algorithm with bytes length
+ And the key name is
+ And the key URL contains the vault url and
+ And the key enabled status is enabled
+ And the key has as operations
+ And the key has as tags
+ And the key was created HSM
+ And the EC specific fields are not populated
+ And the OCT specific fields are not populated
+ And the key recovery settings are default
+
+ Examples:
+ | api | hsm | keyName | keySize | nBytes | enabledStatus | operations | expires | notBefore | tagMap |
+ | 7.3 | without | 73-rotateRsaKey | 2048 | 257 | enabled | null | null | null | null |
+ | 7.3 | without | 73-rotateRsaKeyAllOps | 4096 | 513 | enabled | encrypt,decrypt,wrapKey,unwrapKey,sign,verify,import | null | null | null |
+ | 7.3 | without | 73-rotateRsaKeyNotEnabled | 2048 | 257 | not enabled | null | 4321 | 1234 | null |
+ | 7.3 | without | 73-rotateRsaKeyMap | 2048 | 257 | enabled | null | null | null | aKey:aValue,b1:b2 |
+
+ @Key @KeyCreate @KeyRotate @EC
+ Scenario Outline: EC_ROTATE_01 Single versions of EC keys can be created with the key client, then rotated and result observed
+ Given key API version is used
+ And a key client is created with the vault named keys-generic
+ And an EC key named is prepared with and HSM
+ And the key is set to expire seconds after creation
+ And the key is set to be not usable until seconds after creation
+ And the key is set to use as tags
+ And the key has operations granted
+ And the key is set to be
+ And the EC key is created
+ When the key named is rotated
+ Then the created key is using EC algorithm with curve name and bytes length
+ And the key name is
+ And the key URL contains the vault url and
+ And the key enabled status is enabled
+ And the key has as operations
+ And the key has as tags
+ And the key was created HSM
+ And the RSA specific fields are not populated
+ And the OCT specific fields are not populated
+ And the key recovery settings are default
+
+ Examples:
+ | api | hsm | keyName | curveName | nBytes | enabledStatus | operations | expires | notBefore | tagMap |
+ | 7.3 | without | 73-rotateEcKey256 | P-256 | 32 | enabled | null | null | null | null |
+ | 7.3 | without | 73-rotateEcKeyMap1 | P-256 | 32 | enabled | null | null | null | aKey:aValue,b1:b2 |
+ | 7.3 | without | 73-rotateEcKeyAllOps | P-256 | 32 | enabled | encrypt,decrypt,wrapKey,unwrapKey,sign,verify,import | null | null | null |
+ | 7.3 | without | 73-rotateEcKeyNotEnabled | P-256 | 32 | not enabled | null | null | null | null |
+
+ @Key @KeyCreate @KeyRotate @OCT
+ Scenario Outline: OCT_ROTATE_01 Single versions of OCT keys can be created with the key client, then rotated and result observed
+ Given key API version is used
+ And a key client is created with the vault named keys-generic
+ And an OCT key named is prepared with bits size
+ And the key is set to expire seconds after creation
+ And the key is set to be not usable until seconds after creation
+ And the key is set to use as tags
+ And the key has operations granted
+ And the key is set to be
+ And the OCT key is created
+ When the key named is rotated
+ Then the created key is using OCT algorithm
+ And the key name is
+ And the key URL contains the vault url and
+ And the key enabled status is enabled
+ And the key has as operations
+ And the key has as tags
+ And the key was created HSM
+ And the RSA specific fields are not populated
+ And the EC specific fields are not populated
+ And the OCT specific fields are not populated
+ And the key recovery settings are default
+
+ Examples:
+ | api | hsm | keyName | keySize | enabledStatus | operations | expires | notBefore | tagMap |
+ | 7.3 | with | 73-rotateOctKey | 128 | enabled | null | null | null | null |
+ | 7.3 | with | 73-rotateOctKeyMap1 | 128 | enabled | null | null | null | aKey:aValue,b1:b2 |
+ | 7.3 | with | 73-rotateOctKeyAllOps | 128 | enabled | encrypt,decrypt,wrapKey,unwrapKey,sign,verify,import | null | null | null |
+ | 7.3 | with | 73-rotateOctKeyNotEnabled | 128 | not enabled | null | null | null | null |
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-72.json
index b466454f..c6987b61 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-72.json
@@ -1,23 +1,31 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-256-72.localhost:8443",
- "entityId" : "jsonBackupEc-256-72",
- "entityVersion" : "f16a91376c13478996195a0c2cee39cb",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-256",
- "d" : "IvdJQWa59MJflXPF25Cc4UlOREHZVL5_6sD27nyz1J8",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-256-72.localhost:8443/keys/jsonBackupEc-256-72/f16a91376c13478996195a0c2cee39cb",
- "kty" : "EC",
- "x" : "XBlNkVS33eZqrVgbpeoNerx5XKMixFxFrYl5FJKQ5vQ",
- "y" : "KKoHw5q3QM1lehkqggvNViaX1KBM2ePvmXE2IQbWiLE"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-256-72.localhost:8443",
+ "entityId": "jsonBackupEc-256-72",
+ "entityVersion": "f16a91376c13478996195a0c2cee39cb",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-256",
+ "d": "IvdJQWa59MJflXPF25Cc4UlOREHZVL5_6sD27nyz1J8",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-256-72.localhost:8443/keys/jsonBackupEc-256-72/f16a91376c13478996195a0c2cee39cb",
+ "kty": "EC",
+ "x": "XBlNkVS33eZqrVgbpeoNerx5XKMixFxFrYl5FJKQ5vQ",
+ "y": "KKoHw5q3QM1lehkqggvNViaX1KBM2ePvmXE2IQbWiLE"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-73.json
index 4e285295..84eba7a6 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256-73.json
@@ -1,23 +1,58 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-256-73.localhost:8443",
- "entityId" : "jsonBackupEc-256-73",
- "entityVersion" : "f16a91376c13478996195a0c2cee39cb",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-256",
- "d" : "IvdJQWa59MJflXPF25Cc4UlOREHZVL5_6sD27nyz1J8",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-256-73.localhost:8443/keys/jsonBackupEc-256-73/f16a91376c13478996195a0c2cee39cb",
- "kty" : "EC",
- "x" : "XBlNkVS33eZqrVgbpeoNerx5XKMixFxFrYl5FJKQ5vQ",
- "y" : "KKoHw5q3QM1lehkqggvNViaX1KBM2ePvmXE2IQbWiLE"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-256-73.localhost:8443",
+ "entityId": "jsonBackupEc-256-73",
+ "entityVersion": "f16a91376c13478996195a0c2cee39cb",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-256",
+ "d": "IvdJQWa59MJflXPF25Cc4UlOREHZVL5_6sD27nyz1J8",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-256-73.localhost:8443/keys/jsonBackupEc-256-73/f16a91376c13478996195a0c2cee39cb",
+ "kty": "EC",
+ "x": "XBlNkVS33eZqrVgbpeoNerx5XKMixFxFrYl5FJKQ5vQ",
+ "y": "KKoHw5q3QM1lehkqggvNViaX1KBM2ePvmXE2IQbWiLE"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupEc-256-73.localhost:8443/keys/jsonBackupEc-256-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503879,
+ "updated": 1649503879
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-72.json
index 8984af85..bdce4661 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-72.json
@@ -1,23 +1,31 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-256k-72.localhost:8443",
- "entityId" : "jsonBackupEc-256k-72",
- "entityVersion" : "5425a5872c1f4a9fa96db8f40e6c5d07",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-256K",
- "d" : "AONvayGgRSTtrfA6lkCRC-qYccs_pQh35ZSXssCulkrJ",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-256k-72.localhost:8443/keys/jsonBackupEc-256k-72/5425a5872c1f4a9fa96db8f40e6c5d07",
- "kty" : "EC",
- "x" : "AOSHhd4mwdHNED-SdhTnMasUYuEwLPWpLb8rNx-NV9OH",
- "y" : "DJivtzkKcvFCNe9ZmzCzflbG_CSQrbuDBauJDneZ6Xc"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-256k-72.localhost:8443",
+ "entityId": "jsonBackupEc-256k-72",
+ "entityVersion": "5425a5872c1f4a9fa96db8f40e6c5d07",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-256K",
+ "d": "AONvayGgRSTtrfA6lkCRC-qYccs_pQh35ZSXssCulkrJ",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-256k-72.localhost:8443/keys/jsonBackupEc-256k-72/5425a5872c1f4a9fa96db8f40e6c5d07",
+ "kty": "EC",
+ "x": "AOSHhd4mwdHNED-SdhTnMasUYuEwLPWpLb8rNx-NV9OH",
+ "y": "DJivtzkKcvFCNe9ZmzCzflbG_CSQrbuDBauJDneZ6Xc"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-73.json
index a5fd4441..566c35d4 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-256k-73.json
@@ -1,23 +1,58 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-256k-73.localhost:8443",
- "entityId" : "jsonBackupEc-256k-73",
- "entityVersion" : "5425a5872c1f4a9fa96db8f40e6c5d07",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-256K",
- "d" : "AONvayGgRSTtrfA6lkCRC-qYccs_pQh35ZSXssCulkrJ",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-256k-73.localhost:8443/keys/jsonBackupEc-256k-73/5425a5872c1f4a9fa96db8f40e6c5d07",
- "kty" : "EC",
- "x" : "AOSHhd4mwdHNED-SdhTnMasUYuEwLPWpLb8rNx-NV9OH",
- "y" : "DJivtzkKcvFCNe9ZmzCzflbG_CSQrbuDBauJDneZ6Xc"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-256k-73.localhost:8443",
+ "entityId": "jsonBackupEc-256k-73",
+ "entityVersion": "5425a5872c1f4a9fa96db8f40e6c5d07",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-256K",
+ "d": "AONvayGgRSTtrfA6lkCRC-qYccs_pQh35ZSXssCulkrJ",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-256k-73.localhost:8443/keys/jsonBackupEc-256k-73/5425a5872c1f4a9fa96db8f40e6c5d07",
+ "kty": "EC",
+ "x": "AOSHhd4mwdHNED-SdhTnMasUYuEwLPWpLb8rNx-NV9OH",
+ "y": "DJivtzkKcvFCNe9ZmzCzflbG_CSQrbuDBauJDneZ6Xc"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupEc-256k-73.localhost:8443/keys/jsonBackupEc-256k-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503879,
+ "updated": 1649503879
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-72.json
index c4d1a241..f2629934 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-72.json
@@ -1,23 +1,31 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-384-72.localhost:8443",
- "entityId" : "jsonBackupEc-384-72",
- "entityVersion" : "7f4c0a2ef5454e07a533e597434984a8",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-384",
- "d" : "bd2taaXwxvA_DRUZ1wMT28l8TnaMDz1mn2Z2x_pJT_nkZ11BNS1FFxJvjYHIvoU4",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-384-72.localhost:8443/keys/jsonBackupEc-384-72/7f4c0a2ef5454e07a533e597434984a8",
- "kty" : "EC",
- "x" : "KzD2vTm-aSjXN_RFlY7P78R6hpfdJcSHTC9WM7QXmf0VJro3cXdFOZk6vrx5WDjE",
- "y" : "AKYy8hXwjc0O8mVBXOolUvHklqEV2POLIN6c3EpZIJ-Sz2H_Vce0EoAF320bZhxrfw"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-384-72.localhost:8443",
+ "entityId": "jsonBackupEc-384-72",
+ "entityVersion": "7f4c0a2ef5454e07a533e597434984a8",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-384",
+ "d": "bd2taaXwxvA_DRUZ1wMT28l8TnaMDz1mn2Z2x_pJT_nkZ11BNS1FFxJvjYHIvoU4",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-384-72.localhost:8443/keys/jsonBackupEc-384-72/7f4c0a2ef5454e07a533e597434984a8",
+ "kty": "EC",
+ "x": "KzD2vTm-aSjXN_RFlY7P78R6hpfdJcSHTC9WM7QXmf0VJro3cXdFOZk6vrx5WDjE",
+ "y": "AKYy8hXwjc0O8mVBXOolUvHklqEV2POLIN6c3EpZIJ-Sz2H_Vce0EoAF320bZhxrfw"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-73.json
index e27b2a10..321b9912 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-384-73.json
@@ -1,23 +1,58 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-384-73.localhost:8443",
- "entityId" : "jsonBackupEc-384-73",
- "entityVersion" : "7f4c0a2ef5454e07a533e597434984a8",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-384",
- "d" : "bd2taaXwxvA_DRUZ1wMT28l8TnaMDz1mn2Z2x_pJT_nkZ11BNS1FFxJvjYHIvoU4",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-384-73.localhost:8443/keys/jsonBackupEc-384-73/7f4c0a2ef5454e07a533e597434984a8",
- "kty" : "EC",
- "x" : "KzD2vTm-aSjXN_RFlY7P78R6hpfdJcSHTC9WM7QXmf0VJro3cXdFOZk6vrx5WDjE",
- "y" : "AKYy8hXwjc0O8mVBXOolUvHklqEV2POLIN6c3EpZIJ-Sz2H_Vce0EoAF320bZhxrfw"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-384-73.localhost:8443",
+ "entityId": "jsonBackupEc-384-73",
+ "entityVersion": "7f4c0a2ef5454e07a533e597434984a8",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-384",
+ "d": "bd2taaXwxvA_DRUZ1wMT28l8TnaMDz1mn2Z2x_pJT_nkZ11BNS1FFxJvjYHIvoU4",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-384-73.localhost:8443/keys/jsonBackupEc-384-73/7f4c0a2ef5454e07a533e597434984a8",
+ "kty": "EC",
+ "x": "KzD2vTm-aSjXN_RFlY7P78R6hpfdJcSHTC9WM7QXmf0VJro3cXdFOZk6vrx5WDjE",
+ "y": "AKYy8hXwjc0O8mVBXOolUvHklqEV2POLIN6c3EpZIJ-Sz2H_Vce0EoAF320bZhxrfw"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupEc-384-73.localhost:8443/keys/jsonBackupEc-384-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503879,
+ "updated": 1649503879
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-72.json
index 55e6b332..e5cc79da 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-72.json
@@ -1,23 +1,31 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-521-72.localhost:8443",
- "entityId" : "jsonBackupEc-521-72",
- "entityVersion" : "9301e31d18a540e08c68ad5230e60e21",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-521",
- "d" : "AWNmGqYqot0Zq_6uqnqv2lkA40ke1uTJhehp692dS_r2C7oPmJ0qcPgc31jOOFzc-v69chbMawkR-wyKXFf2O4mh",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-521-72.localhost:8443/keys/jsonBackupEc-521-72/9301e31d18a540e08c68ad5230e60e21",
- "kty" : "EC",
- "x" : "AbDYVOxj0e-tUTgqybst45IXon0axFo_sXlHyJQgYvFDxMNrxa5d4rV7X5H487zh3p_aR_dnVEnbWz9od42mlldE",
- "y" : "ANk3zxFnwHSgsJr2AZvCHe06Zv7LOQdifHIVaZqx72NOf87DQQoEEMXNp3lTMYANkAqlerz80kjGpoBAe1ZyEtsH"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-521-72.localhost:8443",
+ "entityId": "jsonBackupEc-521-72",
+ "entityVersion": "9301e31d18a540e08c68ad5230e60e21",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-521",
+ "d": "AWNmGqYqot0Zq_6uqnqv2lkA40ke1uTJhehp692dS_r2C7oPmJ0qcPgc31jOOFzc-v69chbMawkR-wyKXFf2O4mh",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-521-72.localhost:8443/keys/jsonBackupEc-521-72/9301e31d18a540e08c68ad5230e60e21",
+ "kty": "EC",
+ "x": "AbDYVOxj0e-tUTgqybst45IXon0axFo_sXlHyJQgYvFDxMNrxa5d4rV7X5H487zh3p_aR_dnVEnbWz9od42mlldE",
+ "y": "ANk3zxFnwHSgsJr2AZvCHe06Zv7LOQdifHIVaZqx72NOf87DQQoEEMXNp3lTMYANkAqlerz80kjGpoBAe1ZyEtsH"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-73.json
index 0c3b2281..84fa4d0f 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupEc-521-73.json
@@ -1,23 +1,58 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupEc-521-73.localhost:8443",
- "entityId" : "jsonBackupEc-521-73",
- "entityVersion" : "9301e31d18a540e08c68ad5230e60e21",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503879,
- "updated" : 1649503879,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "crv" : "P-521",
- "d" : "AWNmGqYqot0Zq_6uqnqv2lkA40ke1uTJhehp692dS_r2C7oPmJ0qcPgc31jOOFzc-v69chbMawkR-wyKXFf2O4mh",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupEc-521-73.localhost:8443/keys/jsonBackupEc-521-73/9301e31d18a540e08c68ad5230e60e21",
- "kty" : "EC",
- "x" : "AbDYVOxj0e-tUTgqybst45IXon0axFo_sXlHyJQgYvFDxMNrxa5d4rV7X5H487zh3p_aR_dnVEnbWz9od42mlldE",
- "y" : "ANk3zxFnwHSgsJr2AZvCHe06Zv7LOQdifHIVaZqx72NOf87DQQoEEMXNp3lTMYANkAqlerz80kjGpoBAe1ZyEtsH"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupEc-521-73.localhost:8443",
+ "entityId": "jsonBackupEc-521-73",
+ "entityVersion": "9301e31d18a540e08c68ad5230e60e21",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503879,
+ "updated": 1649503879,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "crv": "P-521",
+ "d": "AWNmGqYqot0Zq_6uqnqv2lkA40ke1uTJhehp692dS_r2C7oPmJ0qcPgc31jOOFzc-v69chbMawkR-wyKXFf2O4mh",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupEc-521-73.localhost:8443/keys/jsonBackupEc-521-73/9301e31d18a540e08c68ad5230e60e21",
+ "kty": "EC",
+ "x": "AbDYVOxj0e-tUTgqybst45IXon0axFo_sXlHyJQgYvFDxMNrxa5d4rV7X5H487zh3p_aR_dnVEnbWz9od42mlldE",
+ "y": "ANk3zxFnwHSgsJr2AZvCHe06Zv7LOQdifHIVaZqx72NOf87DQQoEEMXNp3lTMYANkAqlerz80kjGpoBAe1ZyEtsH"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupEc-521-73.localhost:8443/keys/jsonBackupEc-521-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503879,
+ "updated": 1649503879
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-72.json
index d915b260..299b6ba5 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-72.json
@@ -1,20 +1,28 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-128-72.localhost:8443",
- "entityId" : "jsonBackupOct-128-72",
- "entityVersion" : "730490a0df9b4ac78ed675902077a18f",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "sx32Vta2Zx1BsdQBY2l5iQ",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-128-72.localhost:8443/keys/jsonBackupOct-128-72/730490a0df9b4ac78ed675902077a18f",
- "kty" : "oct-HSM"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-128-72.localhost:8443",
+ "entityId": "jsonBackupOct-128-72",
+ "entityVersion": "730490a0df9b4ac78ed675902077a18f",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "sx32Vta2Zx1BsdQBY2l5iQ",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-128-72.localhost:8443/keys/jsonBackupOct-128-72/730490a0df9b4ac78ed675902077a18f",
+ "kty": "oct-HSM"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-73.json
index c070ccdc..35bccb64 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-128-73.json
@@ -1,20 +1,55 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-128-73.localhost:8443",
- "entityId" : "jsonBackupOct-128-73",
- "entityVersion" : "730490a0df9b4ac78ed675902077a18f",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "sx32Vta2Zx1BsdQBY2l5iQ",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-128-73.localhost:8443/keys/jsonBackupOct-128-73/730490a0df9b4ac78ed675902077a18f",
- "kty" : "oct-HSM"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-128-73.localhost:8443",
+ "entityId": "jsonBackupOct-128-73",
+ "entityVersion": "730490a0df9b4ac78ed675902077a18f",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "sx32Vta2Zx1BsdQBY2l5iQ",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-128-73.localhost:8443/keys/jsonBackupOct-128-73/730490a0df9b4ac78ed675902077a18f",
+ "kty": "oct-HSM"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupOct-128-73.localhost:8443/keys/jsonBackupOct-128-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503106,
+ "updated": 1649503106
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-72.json
index bca9e439..24a6d808 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-72.json
@@ -1,20 +1,28 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-192-72.localhost:8443",
- "entityId" : "jsonBackupOct-192-72",
- "entityVersion" : "55ccab8f94b244ab97d82899340c22dd",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "fp2J-nnMUBZVxCDFdKDxjDJX0F_BIM8P",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-192-72.localhost:8443/keys/jsonBackupOct-192-72/55ccab8f94b244ab97d82899340c22dd",
- "kty" : "oct-HSM"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-192-72.localhost:8443",
+ "entityId": "jsonBackupOct-192-72",
+ "entityVersion": "55ccab8f94b244ab97d82899340c22dd",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "fp2J-nnMUBZVxCDFdKDxjDJX0F_BIM8P",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-192-72.localhost:8443/keys/jsonBackupOct-192-72/55ccab8f94b244ab97d82899340c22dd",
+ "kty": "oct-HSM"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-73.json
index 2fd02eae..95d7be06 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-192-73.json
@@ -1,20 +1,56 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-192-73.localhost:8443",
- "entityId" : "jsonBackupOct-192-73",
- "entityVersion" : "55ccab8f94b244ab97d82899340c22dd",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "fp2J-nnMUBZVxCDFdKDxjDJX0F_BIM8P",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-192-73.localhost:8443/keys/jsonBackupOct-192-73/55ccab8f94b244ab97d82899340c22dd",
- "kty" : "oct-HSM"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-192-73.localhost:8443",
+ "entityId": "jsonBackupOct-192-73",
+ "entityVersion": "55ccab8f94b244ab97d82899340c22dd",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "fp2J-nnMUBZVxCDFdKDxjDJX0F_BIM8P",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-192-73.localhost:8443/keys/jsonBackupOct-192-73/55ccab8f94b244ab97d82899340c22dd",
+ "kty": "oct-HSM"
+ }
+ }
+ ],
+
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupOct-192-73.localhost:8443/keys/jsonBackupOct-192-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503106,
+ "updated": 1649503106
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-72.json
index 96e403a7..475c2cba 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-72.json
@@ -1,20 +1,28 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-256-72.localhost:8443",
- "entityId" : "jsonBackupOct-256-72",
- "entityVersion" : "19c6d13acb844fda8ccb14751252433a",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "nmgrv95gVLVdVQ2xe-RpUf-Eog7y0lT22W3EoBU4-cc",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-256-72.localhost:8443/keys/jsonBackupOct-256-72/19c6d13acb844fda8ccb14751252433a",
- "kty" : "oct-HSM"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-256-72.localhost:8443",
+ "entityId": "jsonBackupOct-256-72",
+ "entityVersion": "19c6d13acb844fda8ccb14751252433a",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "nmgrv95gVLVdVQ2xe-RpUf-Eog7y0lT22W3EoBU4-cc",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-256-72.localhost:8443/keys/jsonBackupOct-256-72/19c6d13acb844fda8ccb14751252433a",
+ "kty": "oct-HSM"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-73.json
index 90c4ca01..b5151034 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupOct-256-73.json
@@ -1,20 +1,56 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupOct-256-73.localhost:8443",
- "entityId" : "jsonBackupOct-256-73",
- "entityVersion" : "19c6d13acb844fda8ccb14751252433a",
- "attributes" : {
- "enabled" : true,
- "created" : 1649503106,
- "updated" : 1649503106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "k" : "nmgrv95gVLVdVQ2xe-RpUf-Eog7y0lT22W3EoBU4-cc",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupOct-256-73.localhost:8443/keys/jsonBackupOct-256-73/19c6d13acb844fda8ccb14751252433a",
- "kty" : "oct-HSM"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupOct-256-73.localhost:8443",
+ "entityId": "jsonBackupOct-256-73",
+ "entityVersion": "19c6d13acb844fda8ccb14751252433a",
+ "attributes": {
+ "enabled": true,
+ "created": 1649503106,
+ "updated": 1649503106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "k": "nmgrv95gVLVdVQ2xe-RpUf-Eog7y0lT22W3EoBU4-cc",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupOct-256-73.localhost:8443/keys/jsonBackupOct-256-73/19c6d13acb844fda8ccb14751252433a",
+ "kty": "oct-HSM"
+ }
+ }
+ ],
+
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupOct-256-73.localhost:8443/keys/jsonBackupOct-256-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649503106,
+ "updated": 1649503106
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-72.json
index 56d1396c..254feb0a 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-72.json
@@ -1,27 +1,35 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-2048-72.localhost:8443",
- "entityId" : "jsonBackupRsa-2048-72",
- "entityVersion" : "2d93f37afada4679b00b528f7238ad5c",
- "attributes" : {
- "enabled" : true,
- "created" : 1649504957,
- "updated" : 1649504957,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "BOSCOBcA_oaZAMPp3APYWbQvyPuw0uFwblDxFSN8YGsSaMqNZMc4b01hC7oMRJzTC7k5dFkCcvikKJU3xWiqNY-FP9HEPJkHezEOQ9rk4nKuJuex157gf1SUxqzDttjQxty027P0ZVL-EmB6OdET2CKVpEyTo7RVZ9UBvQnb1S7-ZBU7UTBotWA7MRsfItvTX_EHX9atPJegpSWmJaJ05-_-dXkRnffrk43bLJ5cJEF4G6X7c4rUFjxSTJ3OwTjmpZPAJT1q85H3-NXrhXiabkFicR3cIj4OpA4egUpRwVze0VAbNOlIsZYIUocLuBelKYLLmr5hTnP_UKSKwznayQ",
- "dp" : "AN9pfqyKw-uT9FWr8GqsyhqJYR9KLgTtX6uqxJk_Y-4NZEMooCeq2olaVpFic0tfaOMJnDufYsUBL_i1TxoD9w3s0YjVwBiURFKs-0Ghs2RGxk22WGbKrnS4Zh8k2RKkNvFPnRciXva1nfMjeWEahb6O8ReA7AZNECKAqJtE5UTB",
- "dq" : "B7sIKKG4bo4Gvm4xj7KBnlP4X9UQH8FLnisGVe9HqDJH4Zu7d-dj5yHT-HaX07MU5p0r2H26owT829SxZGZB2qR_njnuyt8KnmAdEJPttNkW6L25csAoEzqdexi08wmWH0fAEMVDgeobAg1YtByts-VmajO0TWxYyO1oiJFc6ok",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-2048-72.localhost:8443/keys/jsonBackupRsa-2048-72/2d93f37afada4679b00b528f7238ad5c",
- "kty" : "RSA-HSM",
- "n" : "AMfXpr8tkLe86czuJnDBbzcNFNk5paAm0hcI6zD-P1OYvinL2zJG3-8O0GQNuHeExZykhpfOqo6nOv1tbcqesUwkgcT385z2Cmf0G0TfK7EcvDRO8APxqMIvuQpglzFOXAJ4aqVD56IWFMzzkkaR73DKmDF-6cmWtrZu0dEv7LN5cZAk2wd421k5JTbcHoo5kAZBv_CJNt_M0n5VMK5jACV_d53mmaumTHffu-BLOyfBlr0cQMr63aBYh8q5KlcL7U5QJC01umOl4Wwl7yrbD4jZo5f3kWwYTDfeRQlz40h68XUssPc9lG7Gsj9hYxalyCwP9ix5dcs4x-OsYXLX-nM",
- "p" : "AOqsdAb_YNN9OdgFRuVNUmMlG2DpgQfCs8kHUi0_s4IGLjgu4yX1LUhVNnBMLCxTEGO7U-N9e3jHRDexQLHbXzt2IkrN-pwh3MZVEry6yxQILxbMp4aPqZpWTs9ufSiX0iI68lb7uZhsiaMcpEYQX_N67Vlj1Z6-rCGM1K_-86op",
- "q" : "ANoA3nnRaVah2bdmqdjK1Hh20wj4gTEQrKnt_XZDlxMo_HZ77saX_hMjJpYX008w-ZPV-LFNc_clcHK7ZedJZB6pcmuumPvLuvqSqifD4wEwcQAR3a8aTs2gBeqmjyxXXtQk8KK8LNpIzclm98n0vmk1kc0fHobmr_KVFyFBYAs7",
- "qi" : "AOfgFCIEztUVLY7nA5-vMDPRYsf8oKSVeeXDqueXogic75BzYL4mfWevgfkyatP0aa8y8E38annoMN-E1J-cu2gBneLQCd9rf0cIqRIooTwZ-RMLPEKtpBNcT3sBirfeviITpDqOirbke92XbMNGkAEPoC5JPiCYU1FASGDaJFaF"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-2048-72.localhost:8443",
+ "entityId": "jsonBackupRsa-2048-72",
+ "entityVersion": "2d93f37afada4679b00b528f7238ad5c",
+ "attributes": {
+ "enabled": true,
+ "created": 1649504957,
+ "updated": 1649504957,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "BOSCOBcA_oaZAMPp3APYWbQvyPuw0uFwblDxFSN8YGsSaMqNZMc4b01hC7oMRJzTC7k5dFkCcvikKJU3xWiqNY-FP9HEPJkHezEOQ9rk4nKuJuex157gf1SUxqzDttjQxty027P0ZVL-EmB6OdET2CKVpEyTo7RVZ9UBvQnb1S7-ZBU7UTBotWA7MRsfItvTX_EHX9atPJegpSWmJaJ05-_-dXkRnffrk43bLJ5cJEF4G6X7c4rUFjxSTJ3OwTjmpZPAJT1q85H3-NXrhXiabkFicR3cIj4OpA4egUpRwVze0VAbNOlIsZYIUocLuBelKYLLmr5hTnP_UKSKwznayQ",
+ "dp": "AN9pfqyKw-uT9FWr8GqsyhqJYR9KLgTtX6uqxJk_Y-4NZEMooCeq2olaVpFic0tfaOMJnDufYsUBL_i1TxoD9w3s0YjVwBiURFKs-0Ghs2RGxk22WGbKrnS4Zh8k2RKkNvFPnRciXva1nfMjeWEahb6O8ReA7AZNECKAqJtE5UTB",
+ "dq": "B7sIKKG4bo4Gvm4xj7KBnlP4X9UQH8FLnisGVe9HqDJH4Zu7d-dj5yHT-HaX07MU5p0r2H26owT829SxZGZB2qR_njnuyt8KnmAdEJPttNkW6L25csAoEzqdexi08wmWH0fAEMVDgeobAg1YtByts-VmajO0TWxYyO1oiJFc6ok",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-2048-72.localhost:8443/keys/jsonBackupRsa-2048-72/2d93f37afada4679b00b528f7238ad5c",
+ "kty": "RSA-HSM",
+ "n": "AMfXpr8tkLe86czuJnDBbzcNFNk5paAm0hcI6zD-P1OYvinL2zJG3-8O0GQNuHeExZykhpfOqo6nOv1tbcqesUwkgcT385z2Cmf0G0TfK7EcvDRO8APxqMIvuQpglzFOXAJ4aqVD56IWFMzzkkaR73DKmDF-6cmWtrZu0dEv7LN5cZAk2wd421k5JTbcHoo5kAZBv_CJNt_M0n5VMK5jACV_d53mmaumTHffu-BLOyfBlr0cQMr63aBYh8q5KlcL7U5QJC01umOl4Wwl7yrbD4jZo5f3kWwYTDfeRQlz40h68XUssPc9lG7Gsj9hYxalyCwP9ix5dcs4x-OsYXLX-nM",
+ "p": "AOqsdAb_YNN9OdgFRuVNUmMlG2DpgQfCs8kHUi0_s4IGLjgu4yX1LUhVNnBMLCxTEGO7U-N9e3jHRDexQLHbXzt2IkrN-pwh3MZVEry6yxQILxbMp4aPqZpWTs9ufSiX0iI68lb7uZhsiaMcpEYQX_N67Vlj1Z6-rCGM1K_-86op",
+ "q": "ANoA3nnRaVah2bdmqdjK1Hh20wj4gTEQrKnt_XZDlxMo_HZ77saX_hMjJpYX008w-ZPV-LFNc_clcHK7ZedJZB6pcmuumPvLuvqSqifD4wEwcQAR3a8aTs2gBeqmjyxXXtQk8KK8LNpIzclm98n0vmk1kc0fHobmr_KVFyFBYAs7",
+ "qi": "AOfgFCIEztUVLY7nA5-vMDPRYsf8oKSVeeXDqueXogic75BzYL4mfWevgfkyatP0aa8y8E38annoMN-E1J-cu2gBneLQCd9rf0cIqRIooTwZ-RMLPEKtpBNcT3sBirfeviITpDqOirbke92XbMNGkAEPoC5JPiCYU1FASGDaJFaF"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-73.json
index 268cc230..7ef16d8c 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-2048-73.json
@@ -1,27 +1,62 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-2048-73.localhost:8443",
- "entityId" : "jsonBackupRsa-2048-73",
- "entityVersion" : "2d93f37afada4679b00b528f7238ad5c",
- "attributes" : {
- "enabled" : true,
- "created" : 1649504957,
- "updated" : 1649504957,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "BOSCOBcA_oaZAMPp3APYWbQvyPuw0uFwblDxFSN8YGsSaMqNZMc4b01hC7oMRJzTC7k5dFkCcvikKJU3xWiqNY-FP9HEPJkHezEOQ9rk4nKuJuex157gf1SUxqzDttjQxty027P0ZVL-EmB6OdET2CKVpEyTo7RVZ9UBvQnb1S7-ZBU7UTBotWA7MRsfItvTX_EHX9atPJegpSWmJaJ05-_-dXkRnffrk43bLJ5cJEF4G6X7c4rUFjxSTJ3OwTjmpZPAJT1q85H3-NXrhXiabkFicR3cIj4OpA4egUpRwVze0VAbNOlIsZYIUocLuBelKYLLmr5hTnP_UKSKwznayQ",
- "dp" : "AN9pfqyKw-uT9FWr8GqsyhqJYR9KLgTtX6uqxJk_Y-4NZEMooCeq2olaVpFic0tfaOMJnDufYsUBL_i1TxoD9w3s0YjVwBiURFKs-0Ghs2RGxk22WGbKrnS4Zh8k2RKkNvFPnRciXva1nfMjeWEahb6O8ReA7AZNECKAqJtE5UTB",
- "dq" : "B7sIKKG4bo4Gvm4xj7KBnlP4X9UQH8FLnisGVe9HqDJH4Zu7d-dj5yHT-HaX07MU5p0r2H26owT829SxZGZB2qR_njnuyt8KnmAdEJPttNkW6L25csAoEzqdexi08wmWH0fAEMVDgeobAg1YtByts-VmajO0TWxYyO1oiJFc6ok",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-2048-73.localhost:8443/keys/jsonBackupRsa-2048-73/2d93f37afada4679b00b528f7238ad5c",
- "kty" : "RSA-HSM",
- "n" : "AMfXpr8tkLe86czuJnDBbzcNFNk5paAm0hcI6zD-P1OYvinL2zJG3-8O0GQNuHeExZykhpfOqo6nOv1tbcqesUwkgcT385z2Cmf0G0TfK7EcvDRO8APxqMIvuQpglzFOXAJ4aqVD56IWFMzzkkaR73DKmDF-6cmWtrZu0dEv7LN5cZAk2wd421k5JTbcHoo5kAZBv_CJNt_M0n5VMK5jACV_d53mmaumTHffu-BLOyfBlr0cQMr63aBYh8q5KlcL7U5QJC01umOl4Wwl7yrbD4jZo5f3kWwYTDfeRQlz40h68XUssPc9lG7Gsj9hYxalyCwP9ix5dcs4x-OsYXLX-nM",
- "p" : "AOqsdAb_YNN9OdgFRuVNUmMlG2DpgQfCs8kHUi0_s4IGLjgu4yX1LUhVNnBMLCxTEGO7U-N9e3jHRDexQLHbXzt2IkrN-pwh3MZVEry6yxQILxbMp4aPqZpWTs9ufSiX0iI68lb7uZhsiaMcpEYQX_N67Vlj1Z6-rCGM1K_-86op",
- "q" : "ANoA3nnRaVah2bdmqdjK1Hh20wj4gTEQrKnt_XZDlxMo_HZ77saX_hMjJpYX008w-ZPV-LFNc_clcHK7ZedJZB6pcmuumPvLuvqSqifD4wEwcQAR3a8aTs2gBeqmjyxXXtQk8KK8LNpIzclm98n0vmk1kc0fHobmr_KVFyFBYAs7",
- "qi" : "AOfgFCIEztUVLY7nA5-vMDPRYsf8oKSVeeXDqueXogic75BzYL4mfWevgfkyatP0aa8y8E38annoMN-E1J-cu2gBneLQCd9rf0cIqRIooTwZ-RMLPEKtpBNcT3sBirfeviITpDqOirbke92XbMNGkAEPoC5JPiCYU1FASGDaJFaF"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-2048-73.localhost:8443",
+ "entityId": "jsonBackupRsa-2048-73",
+ "entityVersion": "2d93f37afada4679b00b528f7238ad5c",
+ "attributes": {
+ "enabled": true,
+ "created": 1649504957,
+ "updated": 1649504957,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "BOSCOBcA_oaZAMPp3APYWbQvyPuw0uFwblDxFSN8YGsSaMqNZMc4b01hC7oMRJzTC7k5dFkCcvikKJU3xWiqNY-FP9HEPJkHezEOQ9rk4nKuJuex157gf1SUxqzDttjQxty027P0ZVL-EmB6OdET2CKVpEyTo7RVZ9UBvQnb1S7-ZBU7UTBotWA7MRsfItvTX_EHX9atPJegpSWmJaJ05-_-dXkRnffrk43bLJ5cJEF4G6X7c4rUFjxSTJ3OwTjmpZPAJT1q85H3-NXrhXiabkFicR3cIj4OpA4egUpRwVze0VAbNOlIsZYIUocLuBelKYLLmr5hTnP_UKSKwznayQ",
+ "dp": "AN9pfqyKw-uT9FWr8GqsyhqJYR9KLgTtX6uqxJk_Y-4NZEMooCeq2olaVpFic0tfaOMJnDufYsUBL_i1TxoD9w3s0YjVwBiURFKs-0Ghs2RGxk22WGbKrnS4Zh8k2RKkNvFPnRciXva1nfMjeWEahb6O8ReA7AZNECKAqJtE5UTB",
+ "dq": "B7sIKKG4bo4Gvm4xj7KBnlP4X9UQH8FLnisGVe9HqDJH4Zu7d-dj5yHT-HaX07MU5p0r2H26owT829SxZGZB2qR_njnuyt8KnmAdEJPttNkW6L25csAoEzqdexi08wmWH0fAEMVDgeobAg1YtByts-VmajO0TWxYyO1oiJFc6ok",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-2048-73.localhost:8443/keys/jsonBackupRsa-2048-73/2d93f37afada4679b00b528f7238ad5c",
+ "kty": "RSA-HSM",
+ "n": "AMfXpr8tkLe86czuJnDBbzcNFNk5paAm0hcI6zD-P1OYvinL2zJG3-8O0GQNuHeExZykhpfOqo6nOv1tbcqesUwkgcT385z2Cmf0G0TfK7EcvDRO8APxqMIvuQpglzFOXAJ4aqVD56IWFMzzkkaR73DKmDF-6cmWtrZu0dEv7LN5cZAk2wd421k5JTbcHoo5kAZBv_CJNt_M0n5VMK5jACV_d53mmaumTHffu-BLOyfBlr0cQMr63aBYh8q5KlcL7U5QJC01umOl4Wwl7yrbD4jZo5f3kWwYTDfeRQlz40h68XUssPc9lG7Gsj9hYxalyCwP9ix5dcs4x-OsYXLX-nM",
+ "p": "AOqsdAb_YNN9OdgFRuVNUmMlG2DpgQfCs8kHUi0_s4IGLjgu4yX1LUhVNnBMLCxTEGO7U-N9e3jHRDexQLHbXzt2IkrN-pwh3MZVEry6yxQILxbMp4aPqZpWTs9ufSiX0iI68lb7uZhsiaMcpEYQX_N67Vlj1Z6-rCGM1K_-86op",
+ "q": "ANoA3nnRaVah2bdmqdjK1Hh20wj4gTEQrKnt_XZDlxMo_HZ77saX_hMjJpYX008w-ZPV-LFNc_clcHK7ZedJZB6pcmuumPvLuvqSqifD4wEwcQAR3a8aTs2gBeqmjyxXXtQk8KK8LNpIzclm98n0vmk1kc0fHobmr_KVFyFBYAs7",
+ "qi": "AOfgFCIEztUVLY7nA5-vMDPRYsf8oKSVeeXDqueXogic75BzYL4mfWevgfkyatP0aa8y8E38annoMN-E1J-cu2gBneLQCd9rf0cIqRIooTwZ-RMLPEKtpBNcT3sBirfeviITpDqOirbke92XbMNGkAEPoC5JPiCYU1FASGDaJFaF"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupRsa-2048-73.localhost:8443/keys/jsonBackupRsa-2048-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649504966,
+ "updated": 1649504966
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-72.json
index cb651411..851bcd4f 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-72.json
@@ -1,27 +1,35 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-3072-72.localhost:8443",
- "entityId" : "jsonBackupRsa-3072-72",
- "entityVersion" : "5ae349c717644397a14df7ece5eda28f",
- "attributes" : {
- "enabled" : true,
- "created" : 1649506106,
- "updated" : 1649506106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "FwpaGDisPKDVoOzuCws9yGvvK2nu2LGFp1PefmoZ5mabkS2T-IpqdL-UUfpdZllIxWaFWmuN_c21nJXZhA1UL9g1qywUi5D3lKePDB00jVTIfdZSDrrXr_KTZwLQsYZ_iIeyiG1wPX4hWzx-rKFJTEXLEKUc3yWdlIJyFf9olfBy5PG_83Y6DC56RNFSIvqQUg9AhN-6KauP-0SrdBU2csSBkj97viPvhua2mrW7knwC1TajMQVhwn8DidcV1G13nib1ChBjia70Z3b4n_lisK3330BWfuKZAJyEfcUpIPZjFCF3YfAFcKJGA1hBqG5Up4IA02yekMHGtga_TnPPn9Z7RZ-eA6WlaFKgFvY0IKZYVNwkJ88coQxUmmMvgETy-Xz-yQBPLAaQmVW4dhBi9VUGCDHbgKC9n9XK6DNKz6ejhklctehpePrjweTEG4v0sJsSd1TiQBPL60dme3A8jwlLFdSxSjamn8ysDMdVe6Ck5Qtztm2GSRSdC6cb0d3B",
- "dp" : "NgJjxYCpARiZJCwWrjgBLfajs55DykK1Vy6GtSuVhDtit-7vWNstxSnJIErnfv3-MNjQ0hVOOGck-c9cDuvXNNh0Nx8OTBqCOG4EPX6GiZdUpB27gpSmxEUNsa308kEXV-qmIRptQOco0rGY8HrJ7S1ktWQID89V0jVUHBvw9T6Ktc833rtlffQUEdLex3ijerDnqWorAAkKinXH9TwjgKH4FldpQrOe3b4KpslHn7oUeEsYvepOSFaKKyifym5B",
- "dq" : "b6-Ny82SZFzMFz4dADqRLapsic8r6YRgjh0txKWgL-3tnXzF-YTutxRIhSUf_ypcKN3KmRLNK9ZIjB1YtC56CgbTseBABUbw9EuSN-g9T6zMM0XA916N7JmQhDeWxEaGPzLvLB50ZWoFnyyws4mC_lB8E4hWo615zBJ_06A87be5wRK-e5fIuVe9Zknlnuu3DuuP6ZSPP7lYf0A8ms_Eg8RHHUx_1WTNsCnwdPDfY_O1Fsg4MVpXdeqho7s6UouZ",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-3072-72.localhost:8443/keys/jsonBackupRsa-3072-72/5ae349c717644397a14df7ece5eda28f",
- "kty" : "RSA-HSM",
- "n" : "AI2OC6QgoA3x6CzwXr8GPdMUZSOJ3FdKxS598-xoSCbwJGB6a761d0ugcFnqX87RsQAxaEp3OS3xviSGa0ilc-NIeGkbtKjd96bAkgAKxHRB99fkyHAxA-y8QQzvPF7xgx-eKKs0uR_NGFyJDeqOZPzmfdLZydpq8Y_Rmc_uNPxGaHzbTCiumkkNd9OXs1r6U1OPJecnv6tHLYDh8OMLJ3WZNbJ5OqlpOGMShWgoCV1c5olqcBoHQRyMCH31x9fnxKqWJMFnF5B3yimD-8AaTkTo4EEmi5-mrqJ4IDGoUSh9p8hgafv7XNmkms0ZkEUrsXOLuLvvPeyza_Ka9tgJBUDmkrjUu9mTUyRcKaiBHyR5VLxUBmeNtvS_Df_Vqoi4G-6vMQvVgut1pp6bijBjYtKp5b7m8mxtJ6my1nShu-NxAw4JFMQMmh477cJxqzfrBzlfefvFnhtFS8K78t1CJNRuEmDNB4TNlgrzLlvJq9BUzflBD-XvT_9Vj1_oIcGw3Q",
- "p" : "AMVKqqCIRq9jh5SoW5UxLTfNrsjZkK5J4RAxgwcf2AtQXL0dxOUU-FepkASfo3MoYkStP9aJPlZKMJ0iixkgUjL-Fr0cX2tFgkZoFmyDKTmLguGvCEt07NJD633cnzAZ728OMsP6cK_bYcE48TuRtNvnbVq_QPbm7JLJii_qf2HHPL-EdFlBoFTWWWErxYfRd9XwxAgwHvd0FiNrlHRvtUxuc8h1OnYwORhtXPlrx5D0WFSUlWHbgLOEAnp_EI2FQQ",
- "q" : "ALetcnXW5mFbud_4hh_Vw6QHuRrw1l0MYtgeFvVld6pDzAMzoiVbAVLM0rgmk9vVrHta5b5uj2cT08QqU8EXDRHgnC_fOT-ay966AaQmu3RUACeVv0CcRJQB_FzQ4RLUtHoDj7No23jGjLH849NNc8YaTYAWD7ntQEoVorxhZIyxGI7Zaf_pajkgQPKe2rT88SbONK9UTV-iAQ6wr-_W4amWlhpCWvObCHkhWFL01kmJhVDcrHvslCq4hdDuCeD4nQ",
- "qi" : "MLMFbdT1qRanqk8krWVwbhStSNOdznS75gS3PgmrCLmJ63OCkk6O0EczBHrByPQF5P5sha_ec7ZCIFHaYGakTFrk-4vQJTFtFhx5IripRFUQ2NFhLzhDh_P2Tlg_TBjlaA1AF99iuKPoon2s114a5vq_C14Yzx0aTZgn4UzPUTAi5GATnyGknlR0A9bErtxSdLbHkgOTzYfQBMJvtzgbN8JSsT23cuE-bmitsGjokTFhMvyvmH0ZcaFY4RzTKXeQ"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-3072-72.localhost:8443",
+ "entityId": "jsonBackupRsa-3072-72",
+ "entityVersion": "5ae349c717644397a14df7ece5eda28f",
+ "attributes": {
+ "enabled": true,
+ "created": 1649506106,
+ "updated": 1649506106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "FwpaGDisPKDVoOzuCws9yGvvK2nu2LGFp1PefmoZ5mabkS2T-IpqdL-UUfpdZllIxWaFWmuN_c21nJXZhA1UL9g1qywUi5D3lKePDB00jVTIfdZSDrrXr_KTZwLQsYZ_iIeyiG1wPX4hWzx-rKFJTEXLEKUc3yWdlIJyFf9olfBy5PG_83Y6DC56RNFSIvqQUg9AhN-6KauP-0SrdBU2csSBkj97viPvhua2mrW7knwC1TajMQVhwn8DidcV1G13nib1ChBjia70Z3b4n_lisK3330BWfuKZAJyEfcUpIPZjFCF3YfAFcKJGA1hBqG5Up4IA02yekMHGtga_TnPPn9Z7RZ-eA6WlaFKgFvY0IKZYVNwkJ88coQxUmmMvgETy-Xz-yQBPLAaQmVW4dhBi9VUGCDHbgKC9n9XK6DNKz6ejhklctehpePrjweTEG4v0sJsSd1TiQBPL60dme3A8jwlLFdSxSjamn8ysDMdVe6Ck5Qtztm2GSRSdC6cb0d3B",
+ "dp": "NgJjxYCpARiZJCwWrjgBLfajs55DykK1Vy6GtSuVhDtit-7vWNstxSnJIErnfv3-MNjQ0hVOOGck-c9cDuvXNNh0Nx8OTBqCOG4EPX6GiZdUpB27gpSmxEUNsa308kEXV-qmIRptQOco0rGY8HrJ7S1ktWQID89V0jVUHBvw9T6Ktc833rtlffQUEdLex3ijerDnqWorAAkKinXH9TwjgKH4FldpQrOe3b4KpslHn7oUeEsYvepOSFaKKyifym5B",
+ "dq": "b6-Ny82SZFzMFz4dADqRLapsic8r6YRgjh0txKWgL-3tnXzF-YTutxRIhSUf_ypcKN3KmRLNK9ZIjB1YtC56CgbTseBABUbw9EuSN-g9T6zMM0XA916N7JmQhDeWxEaGPzLvLB50ZWoFnyyws4mC_lB8E4hWo615zBJ_06A87be5wRK-e5fIuVe9Zknlnuu3DuuP6ZSPP7lYf0A8ms_Eg8RHHUx_1WTNsCnwdPDfY_O1Fsg4MVpXdeqho7s6UouZ",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-3072-72.localhost:8443/keys/jsonBackupRsa-3072-72/5ae349c717644397a14df7ece5eda28f",
+ "kty": "RSA-HSM",
+ "n": "AI2OC6QgoA3x6CzwXr8GPdMUZSOJ3FdKxS598-xoSCbwJGB6a761d0ugcFnqX87RsQAxaEp3OS3xviSGa0ilc-NIeGkbtKjd96bAkgAKxHRB99fkyHAxA-y8QQzvPF7xgx-eKKs0uR_NGFyJDeqOZPzmfdLZydpq8Y_Rmc_uNPxGaHzbTCiumkkNd9OXs1r6U1OPJecnv6tHLYDh8OMLJ3WZNbJ5OqlpOGMShWgoCV1c5olqcBoHQRyMCH31x9fnxKqWJMFnF5B3yimD-8AaTkTo4EEmi5-mrqJ4IDGoUSh9p8hgafv7XNmkms0ZkEUrsXOLuLvvPeyza_Ka9tgJBUDmkrjUu9mTUyRcKaiBHyR5VLxUBmeNtvS_Df_Vqoi4G-6vMQvVgut1pp6bijBjYtKp5b7m8mxtJ6my1nShu-NxAw4JFMQMmh477cJxqzfrBzlfefvFnhtFS8K78t1CJNRuEmDNB4TNlgrzLlvJq9BUzflBD-XvT_9Vj1_oIcGw3Q",
+ "p": "AMVKqqCIRq9jh5SoW5UxLTfNrsjZkK5J4RAxgwcf2AtQXL0dxOUU-FepkASfo3MoYkStP9aJPlZKMJ0iixkgUjL-Fr0cX2tFgkZoFmyDKTmLguGvCEt07NJD633cnzAZ728OMsP6cK_bYcE48TuRtNvnbVq_QPbm7JLJii_qf2HHPL-EdFlBoFTWWWErxYfRd9XwxAgwHvd0FiNrlHRvtUxuc8h1OnYwORhtXPlrx5D0WFSUlWHbgLOEAnp_EI2FQQ",
+ "q": "ALetcnXW5mFbud_4hh_Vw6QHuRrw1l0MYtgeFvVld6pDzAMzoiVbAVLM0rgmk9vVrHta5b5uj2cT08QqU8EXDRHgnC_fOT-ay966AaQmu3RUACeVv0CcRJQB_FzQ4RLUtHoDj7No23jGjLH849NNc8YaTYAWD7ntQEoVorxhZIyxGI7Zaf_pajkgQPKe2rT88SbONK9UTV-iAQ6wr-_W4amWlhpCWvObCHkhWFL01kmJhVDcrHvslCq4hdDuCeD4nQ",
+ "qi": "MLMFbdT1qRanqk8krWVwbhStSNOdznS75gS3PgmrCLmJ63OCkk6O0EczBHrByPQF5P5sha_ec7ZCIFHaYGakTFrk-4vQJTFtFhx5IripRFUQ2NFhLzhDh_P2Tlg_TBjlaA1AF99iuKPoon2s114a5vq_C14Yzx0aTZgn4UzPUTAi5GATnyGknlR0A9bErtxSdLbHkgOTzYfQBMJvtzgbN8JSsT23cuE-bmitsGjokTFhMvyvmH0ZcaFY4RzTKXeQ"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-73.json
index 16c6ac2d..59302e34 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-3072-73.json
@@ -1,27 +1,62 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-3072-73.localhost:8443",
- "entityId" : "jsonBackupRsa-3072-73",
- "entityVersion" : "5ae349c717644397a14df7ece5eda28f",
- "attributes" : {
- "enabled" : true,
- "created" : 1649506106,
- "updated" : 1649506106,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "FwpaGDisPKDVoOzuCws9yGvvK2nu2LGFp1PefmoZ5mabkS2T-IpqdL-UUfpdZllIxWaFWmuN_c21nJXZhA1UL9g1qywUi5D3lKePDB00jVTIfdZSDrrXr_KTZwLQsYZ_iIeyiG1wPX4hWzx-rKFJTEXLEKUc3yWdlIJyFf9olfBy5PG_83Y6DC56RNFSIvqQUg9AhN-6KauP-0SrdBU2csSBkj97viPvhua2mrW7knwC1TajMQVhwn8DidcV1G13nib1ChBjia70Z3b4n_lisK3330BWfuKZAJyEfcUpIPZjFCF3YfAFcKJGA1hBqG5Up4IA02yekMHGtga_TnPPn9Z7RZ-eA6WlaFKgFvY0IKZYVNwkJ88coQxUmmMvgETy-Xz-yQBPLAaQmVW4dhBi9VUGCDHbgKC9n9XK6DNKz6ejhklctehpePrjweTEG4v0sJsSd1TiQBPL60dme3A8jwlLFdSxSjamn8ysDMdVe6Ck5Qtztm2GSRSdC6cb0d3B",
- "dp" : "NgJjxYCpARiZJCwWrjgBLfajs55DykK1Vy6GtSuVhDtit-7vWNstxSnJIErnfv3-MNjQ0hVOOGck-c9cDuvXNNh0Nx8OTBqCOG4EPX6GiZdUpB27gpSmxEUNsa308kEXV-qmIRptQOco0rGY8HrJ7S1ktWQID89V0jVUHBvw9T6Ktc833rtlffQUEdLex3ijerDnqWorAAkKinXH9TwjgKH4FldpQrOe3b4KpslHn7oUeEsYvepOSFaKKyifym5B",
- "dq" : "b6-Ny82SZFzMFz4dADqRLapsic8r6YRgjh0txKWgL-3tnXzF-YTutxRIhSUf_ypcKN3KmRLNK9ZIjB1YtC56CgbTseBABUbw9EuSN-g9T6zMM0XA916N7JmQhDeWxEaGPzLvLB50ZWoFnyyws4mC_lB8E4hWo615zBJ_06A87be5wRK-e5fIuVe9Zknlnuu3DuuP6ZSPP7lYf0A8ms_Eg8RHHUx_1WTNsCnwdPDfY_O1Fsg4MVpXdeqho7s6UouZ",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-3072-73.localhost:8443/keys/jsonBackupRsa-3072-73/5ae349c717644397a14df7ece5eda28f",
- "kty" : "RSA-HSM",
- "n" : "AI2OC6QgoA3x6CzwXr8GPdMUZSOJ3FdKxS598-xoSCbwJGB6a761d0ugcFnqX87RsQAxaEp3OS3xviSGa0ilc-NIeGkbtKjd96bAkgAKxHRB99fkyHAxA-y8QQzvPF7xgx-eKKs0uR_NGFyJDeqOZPzmfdLZydpq8Y_Rmc_uNPxGaHzbTCiumkkNd9OXs1r6U1OPJecnv6tHLYDh8OMLJ3WZNbJ5OqlpOGMShWgoCV1c5olqcBoHQRyMCH31x9fnxKqWJMFnF5B3yimD-8AaTkTo4EEmi5-mrqJ4IDGoUSh9p8hgafv7XNmkms0ZkEUrsXOLuLvvPeyza_Ka9tgJBUDmkrjUu9mTUyRcKaiBHyR5VLxUBmeNtvS_Df_Vqoi4G-6vMQvVgut1pp6bijBjYtKp5b7m8mxtJ6my1nShu-NxAw4JFMQMmh477cJxqzfrBzlfefvFnhtFS8K78t1CJNRuEmDNB4TNlgrzLlvJq9BUzflBD-XvT_9Vj1_oIcGw3Q",
- "p" : "AMVKqqCIRq9jh5SoW5UxLTfNrsjZkK5J4RAxgwcf2AtQXL0dxOUU-FepkASfo3MoYkStP9aJPlZKMJ0iixkgUjL-Fr0cX2tFgkZoFmyDKTmLguGvCEt07NJD633cnzAZ728OMsP6cK_bYcE48TuRtNvnbVq_QPbm7JLJii_qf2HHPL-EdFlBoFTWWWErxYfRd9XwxAgwHvd0FiNrlHRvtUxuc8h1OnYwORhtXPlrx5D0WFSUlWHbgLOEAnp_EI2FQQ",
- "q" : "ALetcnXW5mFbud_4hh_Vw6QHuRrw1l0MYtgeFvVld6pDzAMzoiVbAVLM0rgmk9vVrHta5b5uj2cT08QqU8EXDRHgnC_fOT-ay966AaQmu3RUACeVv0CcRJQB_FzQ4RLUtHoDj7No23jGjLH849NNc8YaTYAWD7ntQEoVorxhZIyxGI7Zaf_pajkgQPKe2rT88SbONK9UTV-iAQ6wr-_W4amWlhpCWvObCHkhWFL01kmJhVDcrHvslCq4hdDuCeD4nQ",
- "qi" : "MLMFbdT1qRanqk8krWVwbhStSNOdznS75gS3PgmrCLmJ63OCkk6O0EczBHrByPQF5P5sha_ec7ZCIFHaYGakTFrk-4vQJTFtFhx5IripRFUQ2NFhLzhDh_P2Tlg_TBjlaA1AF99iuKPoon2s114a5vq_C14Yzx0aTZgn4UzPUTAi5GATnyGknlR0A9bErtxSdLbHkgOTzYfQBMJvtzgbN8JSsT23cuE-bmitsGjokTFhMvyvmH0ZcaFY4RzTKXeQ"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-3072-73.localhost:8443",
+ "entityId": "jsonBackupRsa-3072-73",
+ "entityVersion": "5ae349c717644397a14df7ece5eda28f",
+ "attributes": {
+ "enabled": true,
+ "created": 1649506106,
+ "updated": 1649506106,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "FwpaGDisPKDVoOzuCws9yGvvK2nu2LGFp1PefmoZ5mabkS2T-IpqdL-UUfpdZllIxWaFWmuN_c21nJXZhA1UL9g1qywUi5D3lKePDB00jVTIfdZSDrrXr_KTZwLQsYZ_iIeyiG1wPX4hWzx-rKFJTEXLEKUc3yWdlIJyFf9olfBy5PG_83Y6DC56RNFSIvqQUg9AhN-6KauP-0SrdBU2csSBkj97viPvhua2mrW7knwC1TajMQVhwn8DidcV1G13nib1ChBjia70Z3b4n_lisK3330BWfuKZAJyEfcUpIPZjFCF3YfAFcKJGA1hBqG5Up4IA02yekMHGtga_TnPPn9Z7RZ-eA6WlaFKgFvY0IKZYVNwkJ88coQxUmmMvgETy-Xz-yQBPLAaQmVW4dhBi9VUGCDHbgKC9n9XK6DNKz6ejhklctehpePrjweTEG4v0sJsSd1TiQBPL60dme3A8jwlLFdSxSjamn8ysDMdVe6Ck5Qtztm2GSRSdC6cb0d3B",
+ "dp": "NgJjxYCpARiZJCwWrjgBLfajs55DykK1Vy6GtSuVhDtit-7vWNstxSnJIErnfv3-MNjQ0hVOOGck-c9cDuvXNNh0Nx8OTBqCOG4EPX6GiZdUpB27gpSmxEUNsa308kEXV-qmIRptQOco0rGY8HrJ7S1ktWQID89V0jVUHBvw9T6Ktc833rtlffQUEdLex3ijerDnqWorAAkKinXH9TwjgKH4FldpQrOe3b4KpslHn7oUeEsYvepOSFaKKyifym5B",
+ "dq": "b6-Ny82SZFzMFz4dADqRLapsic8r6YRgjh0txKWgL-3tnXzF-YTutxRIhSUf_ypcKN3KmRLNK9ZIjB1YtC56CgbTseBABUbw9EuSN-g9T6zMM0XA916N7JmQhDeWxEaGPzLvLB50ZWoFnyyws4mC_lB8E4hWo615zBJ_06A87be5wRK-e5fIuVe9Zknlnuu3DuuP6ZSPP7lYf0A8ms_Eg8RHHUx_1WTNsCnwdPDfY_O1Fsg4MVpXdeqho7s6UouZ",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-3072-73.localhost:8443/keys/jsonBackupRsa-3072-73/5ae349c717644397a14df7ece5eda28f",
+ "kty": "RSA-HSM",
+ "n": "AI2OC6QgoA3x6CzwXr8GPdMUZSOJ3FdKxS598-xoSCbwJGB6a761d0ugcFnqX87RsQAxaEp3OS3xviSGa0ilc-NIeGkbtKjd96bAkgAKxHRB99fkyHAxA-y8QQzvPF7xgx-eKKs0uR_NGFyJDeqOZPzmfdLZydpq8Y_Rmc_uNPxGaHzbTCiumkkNd9OXs1r6U1OPJecnv6tHLYDh8OMLJ3WZNbJ5OqlpOGMShWgoCV1c5olqcBoHQRyMCH31x9fnxKqWJMFnF5B3yimD-8AaTkTo4EEmi5-mrqJ4IDGoUSh9p8hgafv7XNmkms0ZkEUrsXOLuLvvPeyza_Ka9tgJBUDmkrjUu9mTUyRcKaiBHyR5VLxUBmeNtvS_Df_Vqoi4G-6vMQvVgut1pp6bijBjYtKp5b7m8mxtJ6my1nShu-NxAw4JFMQMmh477cJxqzfrBzlfefvFnhtFS8K78t1CJNRuEmDNB4TNlgrzLlvJq9BUzflBD-XvT_9Vj1_oIcGw3Q",
+ "p": "AMVKqqCIRq9jh5SoW5UxLTfNrsjZkK5J4RAxgwcf2AtQXL0dxOUU-FepkASfo3MoYkStP9aJPlZKMJ0iixkgUjL-Fr0cX2tFgkZoFmyDKTmLguGvCEt07NJD633cnzAZ728OMsP6cK_bYcE48TuRtNvnbVq_QPbm7JLJii_qf2HHPL-EdFlBoFTWWWErxYfRd9XwxAgwHvd0FiNrlHRvtUxuc8h1OnYwORhtXPlrx5D0WFSUlWHbgLOEAnp_EI2FQQ",
+ "q": "ALetcnXW5mFbud_4hh_Vw6QHuRrw1l0MYtgeFvVld6pDzAMzoiVbAVLM0rgmk9vVrHta5b5uj2cT08QqU8EXDRHgnC_fOT-ay966AaQmu3RUACeVv0CcRJQB_FzQ4RLUtHoDj7No23jGjLH849NNc8YaTYAWD7ntQEoVorxhZIyxGI7Zaf_pajkgQPKe2rT88SbONK9UTV-iAQ6wr-_W4amWlhpCWvObCHkhWFL01kmJhVDcrHvslCq4hdDuCeD4nQ",
+ "qi": "MLMFbdT1qRanqk8krWVwbhStSNOdznS75gS3PgmrCLmJ63OCkk6O0EczBHrByPQF5P5sha_ec7ZCIFHaYGakTFrk-4vQJTFtFhx5IripRFUQ2NFhLzhDh_P2Tlg_TBjlaA1AF99iuKPoon2s114a5vq_C14Yzx0aTZgn4UzPUTAi5GATnyGknlR0A9bErtxSdLbHkgOTzYfQBMJvtzgbN8JSsT23cuE-bmitsGjokTFhMvyvmH0ZcaFY4RzTKXeQ"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupRsa-3072-73.localhost:8443/keys/jsonBackupRsa-3072-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649504966,
+ "updated": 1649504966
+ }
}
-} ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-72.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-72.json
index b0ad62c6..29fb02aa 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-72.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-72.json
@@ -1,27 +1,35 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-4096-72.localhost:8443",
- "entityId" : "jsonBackupRsa-4096-72",
- "entityVersion" : "6f009cc18cf146cf960a96254e8d3f39",
- "attributes" : {
- "enabled" : true,
- "created" : 1649504966,
- "updated" : 1649504966,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "Hrwbq60uhw31jEz1xm3xuBKcrsun5kzstkxTwkH4QgFvIDszuC1uWN9K46AT10Qr725AyTPTWMwdAXjw01wnuU9gmqcBSjM0kVus2z-WUW_b3qaoTNTmfZ4FkaAzcdslDkeoAu8uRlhbmcuela6pTYSF2vsrIpq-OPCB8PQ4kRU33mmCHk_Ikwx-sjrHqBZqrQubMGCat4n4mgLniHikqv91GK8k0XUDuvwngB6aIMFQ14LoudI12ED7AcP9ia1XwSjSWMRFUzrvJgt2R-jk5iSmZ0y-7WjwR8UBCyHEKEa_ZNP0gtyykSgY2jCwXuC0uRaeSv4AjZOTQuywaV3-aSDjzMx76xxCd2APs3dxkmD07-bRImC1Gcrz21Im7wzskYZTx8TIi6_2PmK6oLZJcewmFLdaveEPSX5KxfSYFNINI5Y9ybsYHA3EBr_vhXYywnvpVOUkRBHcckgxplf7Ehw-P1PFmk1UVsCvhbm2jASbbbG80-nwXocT0X6AimQfKhxgbOJ7I-v71pJD3v3R8UAnkhl8Y3SFwGjbgl_ow6E4OFuuxRZ2LwxFgYJCOnwEE5Xh8Sul9mJB41WN7XmusPqA83kdtJscBne8BmnwmdGaFWoxgxV6k5LNIdIZQox3DBaxgB8BTl5C_NX46TzpnRtpXUQvAK4v564Vc4discU",
- "dp" : "XsWQH0UXj7rUjaaeFYWNP6fclxC5opmLrD4YWYBev3PBvS-cO5EhPL1WQtRicTtTPvabOxlXw9L24jZ6lwmpOkQuWMqnpds_Lpj79KAQTLRrQZ3lRMYsQ20z-ILmzCHaiFxkRK-mxYtVXuQ0-k6C4LDJTMxEWlan3iavG0FNoL44VEZGfw-d7vrUMCCZzOTOgIwi4pT_GLuhy2c7XE-Fs_Fia-MAbMbyTNQb1FcNYytcl3-H5e7pdR40rXV4ISr3ZfgU6lE--kQuK83Sgwkn9QCrwchY7ssK_fxGjv5YWBS9FhNE_u2fxGjo1qzoWRY-PepekuP7IBe-PvUnTMvcNQ",
- "dq" : "Xh3PxcEoe17LVHtyf2qsfbE-PsDLz_petl-94WCVRHD9yOdM8uPIl20cX0paBKNFq9gCpQEag7iO8cJcuuHhdb1i9uBw2wPaSYvUYMWfdWvEmLCMUYNJn428mW6iAiGbvb6uzXTtcJBVC2OxoBEnEeNFlxtcSAuhAtD3sFi15td6gh8g_-Sh7EzBaxwIDS0pVKLs72V60b9OOggl-jcNL_cmaaNq7iAV2FgzOkZM-lkEfrwb_ovu0XdMT4aDaxLgrHHXpYlR6rKg3BBIZdxihR3Ned_6Mv_tJvaEBiu6aTiBMPsSrLZXN5VkO7-nWfP1unvktpuwnwqxyYcqcHdI4Q",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-4096-72.localhost:8443/keys/jsonBackupRsa-4096-72/6f009cc18cf146cf960a96254e8d3f39",
- "kty" : "RSA-HSM",
- "n" : "AKNbPrSp6qQr5wIuHOh7U5i9Mdi0mSLYNfa9vaM2CuY32eqaB1uCcfdgYWP_SHRZDOUgzB2zoTc-30gX4ccHrFHoBMaYvckN9RIEAu0AhToqxm2xr77_9G0CNwgPoohlC2WB69pSYtX17fYfQQVfOPE0K8vo6mSWSgIcsKC9Nenib0DbQLMWPl04f0h_rleYKqyt8ecPpVEOkS9bb0d5p7GbaZCekPLdZ6GS_cYZJbiBqwpNjJoR6QbRCE9JyEo4UHDJ3_TX1rc50X5mAhV4lt_x-f-2YmRhExA7JU0IHQaHDABxeAIZ61bZT6MFNmA14316uAr33MeanjvlXrRyZDQ0QYfeXpDrg7qXJHCwP1kuZmQDWgzX5gUPi_9VqUD1FbxfBDWxjeHvmMVfJqIxRAd5Pk2PtJerIyER3YnmPdFK_Vo_31z84X21NFWTm4M93hudv01rr_cFwRAi3SZZ5ymPMjWbTjVjIyXmLMkgn51g8EwKdxOkv-GtE7NacdorbmcrGlL6CXg6MzbMtIXriqOrjGMTZwKRJXCyE56GblZCxbQpbd5Vm2exG_O_pDmobd6D5dJQBLBAPHhcgbqiVmOy9fcpLeVVEAyVoRnOSGLNKXREDHOxWTZPj-IG9AFl1VAaoW1XwpkwaMOP0Xy-kfKDoJsGaqE0BIY8kiXYHmHz",
- "p" : "ANxECyf5q69UokIVyU-5uFNsvxPuNlIQSiJn8Bcs3nY6E_H-q1Mw56sCxNz2VW07eDQ8vOyYVF6WjHnGfCMeV-F44PpctxpfXySWqAGPy9KmSMsCXlVVlbi6SeEonJR1yuRM4j_8U8npCg-qelSbw6NJIziu5tbmp32LcQK1i3wj43q2bv1ZnYnNg936-EKpOH_skQds3kwtMsb6KQgIlqogMjwTy1d-kPdJx3ZSMhkZp3RfEPQ9zU4XcHH5jOgUH5orRuPI9XI4U_WT4ETQLsGr8EMAXqDNDHJ1ArwwzQSj025hrYym9nxiPg7VDJCXkjjpG6o_fwwjEJomcP4V-r8",
- "q" : "AL3brFARAay4YLokvlYWqWNEnfmQACHWYliew86QygP1LMW6tB5j8ZblfZuRwXUq4m2K_QN_JSr0G1BidFj_6HvMgMIvl5J51Rz7okw6wPw-PCyLu66tUWfBr_9VANNVhtWoORUluE4mf24yiJJFhlv96mV_7qDPmDnilKfP40A3O4VnF8IDq6lf3SN4feXRJofhwPrJbQ0AsXbgy8Jza0i3vrK6oWQhEERx0BwwuRYsfwVc8MP8kzzhaKKJMgCNzbCXUzTFXqcWmIsSgWdbqUVIMx8vHzpDtGhrwbES2meolZo55fadgGS1hLSSx0ciOjxHGe240sKAGeEONwekKc0",
- "qi" : "Izh5IJH37JMCWCMkws4wkx_I2p5YFawshvDQ791kVGKudOALGW9ljggOs6HNlPh09YieCUIfJ9-FRQs4XVaAWcaiCNwCY4tyis0-Z1UNLMrsLlYUFfMSmoGQ_aFj3xkAcFvoj2NKpoYbJA83XvAgCi7bm3r5cPq5DdU3F6yngIx1OjQ5OAikidYK1Sj5_Ue4zZLvSRt6-1BdpePwTBkp9RV7uqIn4jg4LQgC1SkkSZSu5mmSdL3msefrLSkRCxsv3qk92P4OsZpxvFm4Oi_A7uKOQ9d23c6iD5wjyCkl1OTuiPEcdjXJ5y0djNccebk0iZDZrsyLuO9qGAIZMAGCvw"
- }
-} ]
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-4096-72.localhost:8443",
+ "entityId": "jsonBackupRsa-4096-72",
+ "entityVersion": "6f009cc18cf146cf960a96254e8d3f39",
+ "attributes": {
+ "enabled": true,
+ "created": 1649504966,
+ "updated": 1649504966,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "Hrwbq60uhw31jEz1xm3xuBKcrsun5kzstkxTwkH4QgFvIDszuC1uWN9K46AT10Qr725AyTPTWMwdAXjw01wnuU9gmqcBSjM0kVus2z-WUW_b3qaoTNTmfZ4FkaAzcdslDkeoAu8uRlhbmcuela6pTYSF2vsrIpq-OPCB8PQ4kRU33mmCHk_Ikwx-sjrHqBZqrQubMGCat4n4mgLniHikqv91GK8k0XUDuvwngB6aIMFQ14LoudI12ED7AcP9ia1XwSjSWMRFUzrvJgt2R-jk5iSmZ0y-7WjwR8UBCyHEKEa_ZNP0gtyykSgY2jCwXuC0uRaeSv4AjZOTQuywaV3-aSDjzMx76xxCd2APs3dxkmD07-bRImC1Gcrz21Im7wzskYZTx8TIi6_2PmK6oLZJcewmFLdaveEPSX5KxfSYFNINI5Y9ybsYHA3EBr_vhXYywnvpVOUkRBHcckgxplf7Ehw-P1PFmk1UVsCvhbm2jASbbbG80-nwXocT0X6AimQfKhxgbOJ7I-v71pJD3v3R8UAnkhl8Y3SFwGjbgl_ow6E4OFuuxRZ2LwxFgYJCOnwEE5Xh8Sul9mJB41WN7XmusPqA83kdtJscBne8BmnwmdGaFWoxgxV6k5LNIdIZQox3DBaxgB8BTl5C_NX46TzpnRtpXUQvAK4v564Vc4discU",
+ "dp": "XsWQH0UXj7rUjaaeFYWNP6fclxC5opmLrD4YWYBev3PBvS-cO5EhPL1WQtRicTtTPvabOxlXw9L24jZ6lwmpOkQuWMqnpds_Lpj79KAQTLRrQZ3lRMYsQ20z-ILmzCHaiFxkRK-mxYtVXuQ0-k6C4LDJTMxEWlan3iavG0FNoL44VEZGfw-d7vrUMCCZzOTOgIwi4pT_GLuhy2c7XE-Fs_Fia-MAbMbyTNQb1FcNYytcl3-H5e7pdR40rXV4ISr3ZfgU6lE--kQuK83Sgwkn9QCrwchY7ssK_fxGjv5YWBS9FhNE_u2fxGjo1qzoWRY-PepekuP7IBe-PvUnTMvcNQ",
+ "dq": "Xh3PxcEoe17LVHtyf2qsfbE-PsDLz_petl-94WCVRHD9yOdM8uPIl20cX0paBKNFq9gCpQEag7iO8cJcuuHhdb1i9uBw2wPaSYvUYMWfdWvEmLCMUYNJn428mW6iAiGbvb6uzXTtcJBVC2OxoBEnEeNFlxtcSAuhAtD3sFi15td6gh8g_-Sh7EzBaxwIDS0pVKLs72V60b9OOggl-jcNL_cmaaNq7iAV2FgzOkZM-lkEfrwb_ovu0XdMT4aDaxLgrHHXpYlR6rKg3BBIZdxihR3Ned_6Mv_tJvaEBiu6aTiBMPsSrLZXN5VkO7-nWfP1unvktpuwnwqxyYcqcHdI4Q",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-4096-72.localhost:8443/keys/jsonBackupRsa-4096-72/6f009cc18cf146cf960a96254e8d3f39",
+ "kty": "RSA-HSM",
+ "n": "AKNbPrSp6qQr5wIuHOh7U5i9Mdi0mSLYNfa9vaM2CuY32eqaB1uCcfdgYWP_SHRZDOUgzB2zoTc-30gX4ccHrFHoBMaYvckN9RIEAu0AhToqxm2xr77_9G0CNwgPoohlC2WB69pSYtX17fYfQQVfOPE0K8vo6mSWSgIcsKC9Nenib0DbQLMWPl04f0h_rleYKqyt8ecPpVEOkS9bb0d5p7GbaZCekPLdZ6GS_cYZJbiBqwpNjJoR6QbRCE9JyEo4UHDJ3_TX1rc50X5mAhV4lt_x-f-2YmRhExA7JU0IHQaHDABxeAIZ61bZT6MFNmA14316uAr33MeanjvlXrRyZDQ0QYfeXpDrg7qXJHCwP1kuZmQDWgzX5gUPi_9VqUD1FbxfBDWxjeHvmMVfJqIxRAd5Pk2PtJerIyER3YnmPdFK_Vo_31z84X21NFWTm4M93hudv01rr_cFwRAi3SZZ5ymPMjWbTjVjIyXmLMkgn51g8EwKdxOkv-GtE7NacdorbmcrGlL6CXg6MzbMtIXriqOrjGMTZwKRJXCyE56GblZCxbQpbd5Vm2exG_O_pDmobd6D5dJQBLBAPHhcgbqiVmOy9fcpLeVVEAyVoRnOSGLNKXREDHOxWTZPj-IG9AFl1VAaoW1XwpkwaMOP0Xy-kfKDoJsGaqE0BIY8kiXYHmHz",
+ "p": "ANxECyf5q69UokIVyU-5uFNsvxPuNlIQSiJn8Bcs3nY6E_H-q1Mw56sCxNz2VW07eDQ8vOyYVF6WjHnGfCMeV-F44PpctxpfXySWqAGPy9KmSMsCXlVVlbi6SeEonJR1yuRM4j_8U8npCg-qelSbw6NJIziu5tbmp32LcQK1i3wj43q2bv1ZnYnNg936-EKpOH_skQds3kwtMsb6KQgIlqogMjwTy1d-kPdJx3ZSMhkZp3RfEPQ9zU4XcHH5jOgUH5orRuPI9XI4U_WT4ETQLsGr8EMAXqDNDHJ1ArwwzQSj025hrYym9nxiPg7VDJCXkjjpG6o_fwwjEJomcP4V-r8",
+ "q": "AL3brFARAay4YLokvlYWqWNEnfmQACHWYliew86QygP1LMW6tB5j8ZblfZuRwXUq4m2K_QN_JSr0G1BidFj_6HvMgMIvl5J51Rz7okw6wPw-PCyLu66tUWfBr_9VANNVhtWoORUluE4mf24yiJJFhlv96mV_7qDPmDnilKfP40A3O4VnF8IDq6lf3SN4feXRJofhwPrJbQ0AsXbgy8Jza0i3vrK6oWQhEERx0BwwuRYsfwVc8MP8kzzhaKKJMgCNzbCXUzTFXqcWmIsSgWdbqUVIMx8vHzpDtGhrwbES2meolZo55fadgGS1hLSSx0ciOjxHGe240sKAGeEONwekKc0",
+ "qi": "Izh5IJH37JMCWCMkws4wkx_I2p5YFawshvDQ791kVGKudOALGW9ljggOs6HNlPh09YieCUIfJ9-FRQs4XVaAWcaiCNwCY4tyis0-Z1UNLMrsLlYUFfMSmoGQ_aFj3xkAcFvoj2NKpoYbJA83XvAgCi7bm3r5cPq5DdU3F6yngIx1OjQ5OAikidYK1Sj5_Ue4zZLvSRt6-1BdpePwTBkp9RV7uqIn4jg4LQgC1SkkSZSu5mmSdL3msefrLSkRCxsv3qk92P4OsZpxvFm4Oi_A7uKOQ9d23c6iD5wjyCkl1OTuiPEcdjXJ5y0djNccebk0iZDZrsyLuO9qGAIZMAGCvw"
+ }
+ }
+ ]
+}
diff --git a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-73.json b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-73.json
index 3671f817..2cdd9752 100644
--- a/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-73.json
+++ b/lowkey-vault-docker/src/test/resources/json/backups/jsonBackupRsa-4096-73.json
@@ -1,27 +1,62 @@
-[ {
- "vaultBaseUri" : "https://keys-backup-jsonBackupRsa-4096-73.localhost:8443",
- "entityId" : "jsonBackupRsa-4096-73",
- "entityVersion" : "6f009cc18cf146cf960a96254e8d3f39",
- "attributes" : {
- "enabled" : true,
- "created" : 1649504966,
- "updated" : 1649504966,
- "recoveryLevel" : "Recoverable+Purgeable",
- "recoverableDays" : 90
- },
- "tags" : { },
- "managed" : false,
- "keyMaterial" : {
- "d" : "Hrwbq60uhw31jEz1xm3xuBKcrsun5kzstkxTwkH4QgFvIDszuC1uWN9K46AT10Qr725AyTPTWMwdAXjw01wnuU9gmqcBSjM0kVus2z-WUW_b3qaoTNTmfZ4FkaAzcdslDkeoAu8uRlhbmcuela6pTYSF2vsrIpq-OPCB8PQ4kRU33mmCHk_Ikwx-sjrHqBZqrQubMGCat4n4mgLniHikqv91GK8k0XUDuvwngB6aIMFQ14LoudI12ED7AcP9ia1XwSjSWMRFUzrvJgt2R-jk5iSmZ0y-7WjwR8UBCyHEKEa_ZNP0gtyykSgY2jCwXuC0uRaeSv4AjZOTQuywaV3-aSDjzMx76xxCd2APs3dxkmD07-bRImC1Gcrz21Im7wzskYZTx8TIi6_2PmK6oLZJcewmFLdaveEPSX5KxfSYFNINI5Y9ybsYHA3EBr_vhXYywnvpVOUkRBHcckgxplf7Ehw-P1PFmk1UVsCvhbm2jASbbbG80-nwXocT0X6AimQfKhxgbOJ7I-v71pJD3v3R8UAnkhl8Y3SFwGjbgl_ow6E4OFuuxRZ2LwxFgYJCOnwEE5Xh8Sul9mJB41WN7XmusPqA83kdtJscBne8BmnwmdGaFWoxgxV6k5LNIdIZQox3DBaxgB8BTl5C_NX46TzpnRtpXUQvAK4v564Vc4discU",
- "dp" : "XsWQH0UXj7rUjaaeFYWNP6fclxC5opmLrD4YWYBev3PBvS-cO5EhPL1WQtRicTtTPvabOxlXw9L24jZ6lwmpOkQuWMqnpds_Lpj79KAQTLRrQZ3lRMYsQ20z-ILmzCHaiFxkRK-mxYtVXuQ0-k6C4LDJTMxEWlan3iavG0FNoL44VEZGfw-d7vrUMCCZzOTOgIwi4pT_GLuhy2c7XE-Fs_Fia-MAbMbyTNQb1FcNYytcl3-H5e7pdR40rXV4ISr3ZfgU6lE--kQuK83Sgwkn9QCrwchY7ssK_fxGjv5YWBS9FhNE_u2fxGjo1qzoWRY-PepekuP7IBe-PvUnTMvcNQ",
- "dq" : "Xh3PxcEoe17LVHtyf2qsfbE-PsDLz_petl-94WCVRHD9yOdM8uPIl20cX0paBKNFq9gCpQEag7iO8cJcuuHhdb1i9uBw2wPaSYvUYMWfdWvEmLCMUYNJn428mW6iAiGbvb6uzXTtcJBVC2OxoBEnEeNFlxtcSAuhAtD3sFi15td6gh8g_-Sh7EzBaxwIDS0pVKLs72V60b9OOggl-jcNL_cmaaNq7iAV2FgzOkZM-lkEfrwb_ovu0XdMT4aDaxLgrHHXpYlR6rKg3BBIZdxihR3Ned_6Mv_tJvaEBiu6aTiBMPsSrLZXN5VkO7-nWfP1unvktpuwnwqxyYcqcHdI4Q",
- "e" : "AQAB",
- "key_ops" : [ "sign", "encrypt", "wrapKey" ],
- "kid" : "https://keys-backup-jsonBackupRsa-4096-73.localhost:8443/keys/jsonBackupRsa-4096-73/6f009cc18cf146cf960a96254e8d3f39",
- "kty" : "RSA-HSM",
- "n" : "AKNbPrSp6qQr5wIuHOh7U5i9Mdi0mSLYNfa9vaM2CuY32eqaB1uCcfdgYWP_SHRZDOUgzB2zoTc-30gX4ccHrFHoBMaYvckN9RIEAu0AhToqxm2xr77_9G0CNwgPoohlC2WB69pSYtX17fYfQQVfOPE0K8vo6mSWSgIcsKC9Nenib0DbQLMWPl04f0h_rleYKqyt8ecPpVEOkS9bb0d5p7GbaZCekPLdZ6GS_cYZJbiBqwpNjJoR6QbRCE9JyEo4UHDJ3_TX1rc50X5mAhV4lt_x-f-2YmRhExA7JU0IHQaHDABxeAIZ61bZT6MFNmA14316uAr33MeanjvlXrRyZDQ0QYfeXpDrg7qXJHCwP1kuZmQDWgzX5gUPi_9VqUD1FbxfBDWxjeHvmMVfJqIxRAd5Pk2PtJerIyER3YnmPdFK_Vo_31z84X21NFWTm4M93hudv01rr_cFwRAi3SZZ5ymPMjWbTjVjIyXmLMkgn51g8EwKdxOkv-GtE7NacdorbmcrGlL6CXg6MzbMtIXriqOrjGMTZwKRJXCyE56GblZCxbQpbd5Vm2exG_O_pDmobd6D5dJQBLBAPHhcgbqiVmOy9fcpLeVVEAyVoRnOSGLNKXREDHOxWTZPj-IG9AFl1VAaoW1XwpkwaMOP0Xy-kfKDoJsGaqE0BIY8kiXYHmHz",
- "p" : "ANxECyf5q69UokIVyU-5uFNsvxPuNlIQSiJn8Bcs3nY6E_H-q1Mw56sCxNz2VW07eDQ8vOyYVF6WjHnGfCMeV-F44PpctxpfXySWqAGPy9KmSMsCXlVVlbi6SeEonJR1yuRM4j_8U8npCg-qelSbw6NJIziu5tbmp32LcQK1i3wj43q2bv1ZnYnNg936-EKpOH_skQds3kwtMsb6KQgIlqogMjwTy1d-kPdJx3ZSMhkZp3RfEPQ9zU4XcHH5jOgUH5orRuPI9XI4U_WT4ETQLsGr8EMAXqDNDHJ1ArwwzQSj025hrYym9nxiPg7VDJCXkjjpG6o_fwwjEJomcP4V-r8",
- "q" : "AL3brFARAay4YLokvlYWqWNEnfmQACHWYliew86QygP1LMW6tB5j8ZblfZuRwXUq4m2K_QN_JSr0G1BidFj_6HvMgMIvl5J51Rz7okw6wPw-PCyLu66tUWfBr_9VANNVhtWoORUluE4mf24yiJJFhlv96mV_7qDPmDnilKfP40A3O4VnF8IDq6lf3SN4feXRJofhwPrJbQ0AsXbgy8Jza0i3vrK6oWQhEERx0BwwuRYsfwVc8MP8kzzhaKKJMgCNzbCXUzTFXqcWmIsSgWdbqUVIMx8vHzpDtGhrwbES2meolZo55fadgGS1hLSSx0ciOjxHGe240sKAGeEONwekKc0",
- "qi" : "Izh5IJH37JMCWCMkws4wkx_I2p5YFawshvDQ791kVGKudOALGW9ljggOs6HNlPh09YieCUIfJ9-FRQs4XVaAWcaiCNwCY4tyis0-Z1UNLMrsLlYUFfMSmoGQ_aFj3xkAcFvoj2NKpoYbJA83XvAgCi7bm3r5cPq5DdU3F6yngIx1OjQ5OAikidYK1Sj5_Ue4zZLvSRt6-1BdpePwTBkp9RV7uqIn4jg4LQgC1SkkSZSu5mmSdL3msefrLSkRCxsv3qk92P4OsZpxvFm4Oi_A7uKOQ9d23c6iD5wjyCkl1OTuiPEcdjXJ5y0djNccebk0iZDZrsyLuO9qGAIZMAGCvw"
+{
+ "versions": [
+ {
+ "vaultBaseUri": "https://keys-backup-jsonBackupRsa-4096-73.localhost:8443",
+ "entityId": "jsonBackupRsa-4096-73",
+ "entityVersion": "6f009cc18cf146cf960a96254e8d3f39",
+ "attributes": {
+ "enabled": true,
+ "created": 1649504966,
+ "updated": 1649504966,
+ "recoveryLevel": "Recoverable+Purgeable",
+ "exp": 1659871879,
+ "recoverableDays": 90
+ },
+ "tags": {},
+ "managed": false,
+ "keyMaterial": {
+ "d": "Hrwbq60uhw31jEz1xm3xuBKcrsun5kzstkxTwkH4QgFvIDszuC1uWN9K46AT10Qr725AyTPTWMwdAXjw01wnuU9gmqcBSjM0kVus2z-WUW_b3qaoTNTmfZ4FkaAzcdslDkeoAu8uRlhbmcuela6pTYSF2vsrIpq-OPCB8PQ4kRU33mmCHk_Ikwx-sjrHqBZqrQubMGCat4n4mgLniHikqv91GK8k0XUDuvwngB6aIMFQ14LoudI12ED7AcP9ia1XwSjSWMRFUzrvJgt2R-jk5iSmZ0y-7WjwR8UBCyHEKEa_ZNP0gtyykSgY2jCwXuC0uRaeSv4AjZOTQuywaV3-aSDjzMx76xxCd2APs3dxkmD07-bRImC1Gcrz21Im7wzskYZTx8TIi6_2PmK6oLZJcewmFLdaveEPSX5KxfSYFNINI5Y9ybsYHA3EBr_vhXYywnvpVOUkRBHcckgxplf7Ehw-P1PFmk1UVsCvhbm2jASbbbG80-nwXocT0X6AimQfKhxgbOJ7I-v71pJD3v3R8UAnkhl8Y3SFwGjbgl_ow6E4OFuuxRZ2LwxFgYJCOnwEE5Xh8Sul9mJB41WN7XmusPqA83kdtJscBne8BmnwmdGaFWoxgxV6k5LNIdIZQox3DBaxgB8BTl5C_NX46TzpnRtpXUQvAK4v564Vc4discU",
+ "dp": "XsWQH0UXj7rUjaaeFYWNP6fclxC5opmLrD4YWYBev3PBvS-cO5EhPL1WQtRicTtTPvabOxlXw9L24jZ6lwmpOkQuWMqnpds_Lpj79KAQTLRrQZ3lRMYsQ20z-ILmzCHaiFxkRK-mxYtVXuQ0-k6C4LDJTMxEWlan3iavG0FNoL44VEZGfw-d7vrUMCCZzOTOgIwi4pT_GLuhy2c7XE-Fs_Fia-MAbMbyTNQb1FcNYytcl3-H5e7pdR40rXV4ISr3ZfgU6lE--kQuK83Sgwkn9QCrwchY7ssK_fxGjv5YWBS9FhNE_u2fxGjo1qzoWRY-PepekuP7IBe-PvUnTMvcNQ",
+ "dq": "Xh3PxcEoe17LVHtyf2qsfbE-PsDLz_petl-94WCVRHD9yOdM8uPIl20cX0paBKNFq9gCpQEag7iO8cJcuuHhdb1i9uBw2wPaSYvUYMWfdWvEmLCMUYNJn428mW6iAiGbvb6uzXTtcJBVC2OxoBEnEeNFlxtcSAuhAtD3sFi15td6gh8g_-Sh7EzBaxwIDS0pVKLs72V60b9OOggl-jcNL_cmaaNq7iAV2FgzOkZM-lkEfrwb_ovu0XdMT4aDaxLgrHHXpYlR6rKg3BBIZdxihR3Ned_6Mv_tJvaEBiu6aTiBMPsSrLZXN5VkO7-nWfP1unvktpuwnwqxyYcqcHdI4Q",
+ "e": "AQAB",
+ "key_ops": [
+ "sign",
+ "encrypt",
+ "wrapKey"
+ ],
+ "kid": "https://keys-backup-jsonBackupRsa-4096-73.localhost:8443/keys/jsonBackupRsa-4096-73/6f009cc18cf146cf960a96254e8d3f39",
+ "kty": "RSA-HSM",
+ "n": "AKNbPrSp6qQr5wIuHOh7U5i9Mdi0mSLYNfa9vaM2CuY32eqaB1uCcfdgYWP_SHRZDOUgzB2zoTc-30gX4ccHrFHoBMaYvckN9RIEAu0AhToqxm2xr77_9G0CNwgPoohlC2WB69pSYtX17fYfQQVfOPE0K8vo6mSWSgIcsKC9Nenib0DbQLMWPl04f0h_rleYKqyt8ecPpVEOkS9bb0d5p7GbaZCekPLdZ6GS_cYZJbiBqwpNjJoR6QbRCE9JyEo4UHDJ3_TX1rc50X5mAhV4lt_x-f-2YmRhExA7JU0IHQaHDABxeAIZ61bZT6MFNmA14316uAr33MeanjvlXrRyZDQ0QYfeXpDrg7qXJHCwP1kuZmQDWgzX5gUPi_9VqUD1FbxfBDWxjeHvmMVfJqIxRAd5Pk2PtJerIyER3YnmPdFK_Vo_31z84X21NFWTm4M93hudv01rr_cFwRAi3SZZ5ymPMjWbTjVjIyXmLMkgn51g8EwKdxOkv-GtE7NacdorbmcrGlL6CXg6MzbMtIXriqOrjGMTZwKRJXCyE56GblZCxbQpbd5Vm2exG_O_pDmobd6D5dJQBLBAPHhcgbqiVmOy9fcpLeVVEAyVoRnOSGLNKXREDHOxWTZPj-IG9AFl1VAaoW1XwpkwaMOP0Xy-kfKDoJsGaqE0BIY8kiXYHmHz",
+ "p": "ANxECyf5q69UokIVyU-5uFNsvxPuNlIQSiJn8Bcs3nY6E_H-q1Mw56sCxNz2VW07eDQ8vOyYVF6WjHnGfCMeV-F44PpctxpfXySWqAGPy9KmSMsCXlVVlbi6SeEonJR1yuRM4j_8U8npCg-qelSbw6NJIziu5tbmp32LcQK1i3wj43q2bv1ZnYnNg936-EKpOH_skQds3kwtMsb6KQgIlqogMjwTy1d-kPdJx3ZSMhkZp3RfEPQ9zU4XcHH5jOgUH5orRuPI9XI4U_WT4ETQLsGr8EMAXqDNDHJ1ArwwzQSj025hrYym9nxiPg7VDJCXkjjpG6o_fwwjEJomcP4V-r8",
+ "q": "AL3brFARAay4YLokvlYWqWNEnfmQACHWYliew86QygP1LMW6tB5j8ZblfZuRwXUq4m2K_QN_JSr0G1BidFj_6HvMgMIvl5J51Rz7okw6wPw-PCyLu66tUWfBr_9VANNVhtWoORUluE4mf24yiJJFhlv96mV_7qDPmDnilKfP40A3O4VnF8IDq6lf3SN4feXRJofhwPrJbQ0AsXbgy8Jza0i3vrK6oWQhEERx0BwwuRYsfwVc8MP8kzzhaKKJMgCNzbCXUzTFXqcWmIsSgWdbqUVIMx8vHzpDtGhrwbES2meolZo55fadgGS1hLSSx0ciOjxHGe240sKAGeEONwekKc0",
+ "qi": "Izh5IJH37JMCWCMkws4wkx_I2p5YFawshvDQ791kVGKudOALGW9ljggOs6HNlPh09YieCUIfJ9-FRQs4XVaAWcaiCNwCY4tyis0-Z1UNLMrsLlYUFfMSmoGQ_aFj3xkAcFvoj2NKpoYbJA83XvAgCi7bm3r5cPq5DdU3F6yngIx1OjQ5OAikidYK1Sj5_Ue4zZLvSRt6-1BdpePwTBkp9RV7uqIn4jg4LQgC1SkkSZSu5mmSdL3msefrLSkRCxsv3qk92P4OsZpxvFm4Oi_A7uKOQ9d23c6iD5wjyCkl1OTuiPEcdjXJ5y0djNccebk0iZDZrsyLuO9qGAIZMAGCvw"
+ }
+ }
+ ],
+ "rotationPolicy": {
+ "id": "https://keys-backup-jsonBackupRsa-4096-73.localhost:8443/keys/jsonBackupRsa-4096-73/rotationpolicy",
+ "lifetimeActions": [
+ {
+ "trigger": {
+ "timeBeforeExpiry": "P30D"
+ },
+ "action": {
+ "type": "notify"
+ }
+ },
+ {
+ "trigger": {
+ "timeAfterCreate": "P100D"
+ },
+ "action": {
+ "type": "rotate"
+ }
+ }
+ ],
+ "attributes": {
+ "expiryTime": "P120D",
+ "created": 1649504966,
+ "updated": 1649504966
+ }
}
-} ]
+}