diff --git a/build.gradle b/build.gradle
index 2c27c3c..460d411 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
plugins {
id 'rpki-ripe-ncc.build-conventions'
- id 'org.springframework.boot' version '2.7.16'
+ id 'org.springframework.boot' version '2.7.18'
id 'distribution'
id 'jacoco'
id "com.google.cloud.tools.jib" version "3.3.2"
@@ -93,13 +93,6 @@ java {
}
sourceSets {
- main {
- resources {
- srcDir 'public'
- // Wicket resources are in src/main/java
- srcDir 'src/main/java'
- }
- }
integration {
java.srcDir 'src/integration/java'
resources.srcDir 'src/integration/resources'
diff --git a/buildSrc/src/main/groovy/rpki-ripe-ncc.build-conventions.gradle b/buildSrc/src/main/groovy/rpki-ripe-ncc.build-conventions.gradle
index c3c18a6..dd43574 100644
--- a/buildSrc/src/main/groovy/rpki-ripe-ncc.build-conventions.gradle
+++ b/buildSrc/src/main/groovy/rpki-ripe-ncc.build-conventions.gradle
@@ -10,13 +10,26 @@ group = 'net.ripe.rpki-ripe-ncc'
repositories {
mavenLocal()
- mavenCentral()
+ mavenCentral() {
+ content {
+ excludeGroupByRegex "com\\.thales\\.esecurity\\.*"
+ excludeGroupByRegex "com\\.ncipher\\.nfast\\.*"
+ }
+ }
maven {
url = uri('https://oss.sonatype.org/content/repositories/releases')
+ content {
+ excludeGroupByRegex "com\\.thales\\.esecurity\\.*"
+ excludeGroupByRegex "com\\.ncipher\\.nfast\\.*"
+ }
}
maven {
url = uri('https://oss.sonatype.org/content/repositories/snapshots')
+ content {
+ excludeGroupByRegex "com\\.thales\\.esecurity\\.*"
+ excludeGroupByRegex "com\\.ncipher\\.nfast\\.*"
+ }
}
maven {
diff --git a/dependencies.gradle b/dependencies.gradle
index ab855f0..507f781 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,4 +1,4 @@
ext {
- rpki_commons_version = '1.34'
+ rpki_commons_version = '1.36'
spring_boot_version = '2.7.16'
}
diff --git a/src/main/java/net/ripe/rpki/core/write/services/command/CommandServiceImpl.java b/src/main/java/net/ripe/rpki/core/write/services/command/CommandServiceImpl.java
index 20dcf6e..f2f1831 100644
--- a/src/main/java/net/ripe/rpki/core/write/services/command/CommandServiceImpl.java
+++ b/src/main/java/net/ripe/rpki/core/write/services/command/CommandServiceImpl.java
@@ -10,13 +10,12 @@
import net.ripe.rpki.core.events.CertificateAuthorityEventVisitor;
import net.ripe.rpki.domain.ManagedCertificateAuthority;
import net.ripe.rpki.domain.audit.CommandAuditService;
+import net.ripe.rpki.rest.exception.PreconditionRequiredException;
import net.ripe.rpki.ripencc.support.event.EventDelegateTracker;
import net.ripe.rpki.ripencc.support.event.EventSubscription;
import net.ripe.rpki.server.api.commands.CertificateAuthorityCommand;
import net.ripe.rpki.server.api.commands.CommandContext;
-import net.ripe.rpki.server.api.services.command.CommandService;
-import net.ripe.rpki.server.api.services.command.CommandStatus;
-import net.ripe.rpki.server.api.services.command.CommandWithoutEffectException;
+import net.ripe.rpki.server.api.services.command.*;
import net.ripe.rpki.services.impl.handlers.CommandHandlerMetrics;
import net.ripe.rpki.services.impl.handlers.LockCertificateAuthorityHandler;
import net.ripe.rpki.services.impl.handlers.MessageDispatcher;
@@ -127,6 +126,10 @@ private CommandStatus executeCommandWithRetries(CertificateAuthorityCommand comm
log.info("Command failed with possibly transient locking exception {}, retry {} in {} ms: {}", e.getClass().getName(), retryCount, sleepForMs, command);
sleepUninterruptibly(sleepForMs, TimeUnit.MILLISECONDS);
}
+ } catch (EntityTagDoesNotMatchException | PreconditionRequiredException | IllegalResourceException | NotHolderOfResourcesException | PrivateAsnsUsedException e) {
+ // Do not log these user generated commands: This causes very noisy logs.
+ log.info("Aborted a (user) command: {} for reason {}", command, e.getMessage());
+ throw e;
} catch (Exception e) {
log.warn("Error processing command: {}", command, e);
throw e;
diff --git a/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java b/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java
index c115596..4b91adb 100644
--- a/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java
+++ b/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java
@@ -40,6 +40,12 @@ public abstract class GenericPublishedObject extends EntitySupport {
@NonNull
protected byte[] content = new byte[0];
+ /**
+ * The time at which this object was created.
+ *
+ * Do not parse the object for this value if an approximate value is available since parsing violates
+ * abstraction layers.
+ */
@Column(name = "created_at", nullable = false)
@Getter
private Instant createdAt;
diff --git a/src/main/java/net/ripe/rpki/domain/ManagedCertificateAuthority.java b/src/main/java/net/ripe/rpki/domain/ManagedCertificateAuthority.java
index 8ccb40f..c44ac57 100644
--- a/src/main/java/net/ripe/rpki/domain/ManagedCertificateAuthority.java
+++ b/src/main/java/net/ripe/rpki/domain/ManagedCertificateAuthority.java
@@ -338,8 +338,9 @@ private void activatePendingKey(KeyPairEntity newKeyPair) {
* @return false if NO key was activated
*/
public boolean activatePendingKeys(Duration minStagingTime) {
+ DateTime cutOffTime = new DateTime().minus(minStagingTime);
return findPendingKeyPair()
- .filter(pkp -> pkp.getStatusChangedAt(KeyPairStatus.PENDING).isBefore(new DateTime().minus(minStagingTime)))
+ .filter(pkp -> pkp.getStatusChangedAt(KeyPairStatus.PENDING).isBefore(cutOffTime))
.map(pkp -> {
activatePendingKey(pkp);
return true;
diff --git a/src/main/java/net/ripe/rpki/domain/PublishedObject.java b/src/main/java/net/ripe/rpki/domain/PublishedObject.java
index 4eb0836..dc3fed7 100644
--- a/src/main/java/net/ripe/rpki/domain/PublishedObject.java
+++ b/src/main/java/net/ripe/rpki/domain/PublishedObject.java
@@ -5,7 +5,7 @@
import net.ripe.rpki.commons.crypto.ValidityPeriod;
import net.ripe.rpki.domain.manifest.ManifestEntity;
import org.apache.commons.lang3.Validate;
-import org.joda.time.Instant;
+import org.joda.time.DateTime;
import javax.persistence.*;
import java.net.URI;
@@ -63,14 +63,15 @@ protected PublishedObject() {
}
public PublishedObject(
- @NonNull KeyPairEntity issuingKeyPair,
- @NonNull String filename,
- byte[] content,
- boolean includedInManifest,
- @NonNull URI publicationDirectory,
- @NonNull ValidityPeriod validityPeriod
+ @NonNull KeyPairEntity issuingKeyPair,
+ @NonNull String filename,
+ byte[] content,
+ boolean includedInManifest,
+ @NonNull URI publicationDirectory,
+ @NonNull ValidityPeriod validityPeriod,
+ @NonNull DateTime createdAt
) {
- super(content, validityPeriod.getNotValidBefore().toInstant());
+ super(content, createdAt.toInstant());
this.issuingKeyPair = issuingKeyPair;
this.filename = filename;
this.includedInManifest = includedInManifest;
@@ -79,6 +80,22 @@ public PublishedObject(
this.validityPeriod = new EmbeddedValidityPeriod(validityPeriod);
}
+ /**
+ * Construct a PublishedObject with implicit createdAt from the validity period.
+ *
+ * Do not use for CMS signed objects or CRLs
+ */
+ public PublishedObject(
+ @NonNull KeyPairEntity issuingKeyPair,
+ @NonNull String filename,
+ byte[] content,
+ boolean includedInManifest,
+ @NonNull URI publicationDirectory,
+ @NonNull ValidityPeriod validityPeriod
+ ) {
+ this(issuingKeyPair, filename, content, includedInManifest, publicationDirectory, validityPeriod, validityPeriod.getNotValidBefore());
+ }
+
@NonNull
public URI getUri() {
return URI.create(directory).resolve(filename);
diff --git a/src/main/java/net/ripe/rpki/domain/aspa/AspaConfiguration.java b/src/main/java/net/ripe/rpki/domain/aspa/AspaConfiguration.java
index 7036ea4..6375b9f 100644
--- a/src/main/java/net/ripe/rpki/domain/aspa/AspaConfiguration.java
+++ b/src/main/java/net/ripe/rpki/domain/aspa/AspaConfiguration.java
@@ -1,37 +1,19 @@
package net.ripe.rpki.domain.aspa;
+import com.google.common.collect.ImmutableSortedSet;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.ripe.ipresource.Asn;
import net.ripe.rpki.domain.ManagedCertificateAuthority;
import net.ripe.rpki.ncc.core.domain.support.EntitySupport;
-import net.ripe.rpki.server.api.dto.AspaAfiLimit;
import net.ripe.rpki.server.api.dto.AspaConfigurationData;
-import net.ripe.rpki.server.api.dto.AspaProviderData;
-import javax.persistence.CollectionTable;
-import javax.persistence.Column;
-import javax.persistence.ElementCollection;
-import javax.persistence.Entity;
-import javax.persistence.EnumType;
-import javax.persistence.Enumerated;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.MapKeyColumn;
-import javax.persistence.OneToOne;
-import javax.persistence.OrderBy;
-import javax.persistence.SequenceGenerator;
-import javax.persistence.Table;
+import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
-import java.util.Collections;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
+import java.util.*;
import java.util.stream.Collectors;
import static net.ripe.rpki.util.Streams.streamToSortedMap;
@@ -58,25 +40,20 @@ public class AspaConfiguration extends EntitySupport {
private Asn customerAsn;
/**
- * Set of provider ASNs with the AFI limit (null allows both IPv4 and IPv6, otherwise only the specified address
- * family is allowed).
+ * Set of provider ASNs.
*/
- @ElementCollection(fetch = FetchType.EAGER)
- @MapKeyColumn(name = "provider_asn")
- @Enumerated(value = EnumType.STRING)
- @Column(name = "afi_limit")
- @CollectionTable(name = "aspaconfiguration_providers", joinColumns = @JoinColumn(name = "aspaconfiguration_id"))
- @OrderBy("provider_asn")
@NotEmpty
- private SortedMap<@NonNull Asn, @NonNull AspaAfiLimit> providers = new TreeMap<>();
+ @ElementCollection(fetch = FetchType.EAGER)
+ @CollectionTable(name = "aspaconfiguration_providers")
+ private Set<@NonNull Asn> providers = new TreeSet<>();
- public AspaConfiguration(@NonNull ManagedCertificateAuthority certificateAuthority, @NonNull Asn customerAsn, @NonNull Map providers) {
+ public AspaConfiguration(@NonNull ManagedCertificateAuthority certificateAuthority, @NonNull Asn customerAsn, @NonNull SortedSet providers) {
this.certificateAuthority = certificateAuthority;
this.customerAsn = customerAsn;
- this.providers.putAll(providers);
+ this.providers.addAll(providers);
}
- public static SortedMap> entitiesToMaps(SortedMap entities) {
+ public static SortedMap> entitiesToMaps(SortedMap entities) {
return streamToSortedMap(
entities.values().stream(),
AspaConfiguration::getCustomerAsn,
@@ -84,20 +61,18 @@ public static SortedMap> entitiesToMaps(Sorted
);
}
- public SortedMap getProviders() {
- return Collections.unmodifiableSortedMap(providers);
+ public SortedSet getProviders() {
+ return ImmutableSortedSet.copyOf(providers);
}
- public void setProviders(Map providers) {
- this.providers = new TreeMap<>(providers);
+ public void setProviders(SortedSet providers) {
+ this.providers = new TreeSet<>(providers);
}
public AspaConfigurationData toData() {
return new AspaConfigurationData(
getCustomerAsn(),
- providers.entrySet().stream()
- .map(entry -> new AspaProviderData(entry.getKey(), entry.getValue()))
- .collect(Collectors.toList())
+ List.copyOf(getProviders())
);
}
}
diff --git a/src/main/java/net/ripe/rpki/domain/aspa/AspaConfigurationRepository.java b/src/main/java/net/ripe/rpki/domain/aspa/AspaConfigurationRepository.java
index 2bd3fe2..e728d59 100644
--- a/src/main/java/net/ripe/rpki/domain/aspa/AspaConfigurationRepository.java
+++ b/src/main/java/net/ripe/rpki/domain/aspa/AspaConfigurationRepository.java
@@ -9,6 +9,8 @@
public interface AspaConfigurationRepository {
SortedMap findByCertificateAuthority(ManagedCertificateAuthority certificateAuthority);
+ SortedMap findConfigurationsWithProvidersByCertificateAuthority(ManagedCertificateAuthority certificateAuthority);
+
Collection findAll();
void add(AspaConfiguration aspaConfiguration);
diff --git a/src/main/java/net/ripe/rpki/domain/aspa/AspaEntity.java b/src/main/java/net/ripe/rpki/domain/aspa/AspaEntity.java
index d371581..a4e5038 100644
--- a/src/main/java/net/ripe/rpki/domain/aspa/AspaEntity.java
+++ b/src/main/java/net/ripe/rpki/domain/aspa/AspaEntity.java
@@ -1,33 +1,24 @@
package net.ripe.rpki.domain.aspa;
+import com.google.common.collect.ImmutableSortedSet;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
import net.ripe.ipresource.Asn;
import net.ripe.rpki.commons.crypto.cms.aspa.AspaCms;
import net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsParser;
-import net.ripe.rpki.commons.crypto.cms.aspa.ProviderAS;
import net.ripe.rpki.commons.validation.ValidationResult;
import net.ripe.rpki.domain.OutgoingResourceCertificate;
import net.ripe.rpki.domain.PublishedObject;
import net.ripe.rpki.ncc.core.domain.support.EntitySupport;
-import net.ripe.rpki.server.api.dto.AspaAfiLimit;
import net.ripe.rpki.server.api.services.command.UnparseableRpkiObjectException;
import org.apache.commons.lang.Validate;
-import javax.persistence.CascadeType;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.OneToOne;
-import javax.persistence.SequenceGenerator;
-import javax.persistence.Table;
-import javax.persistence.Transient;
+import javax.persistence.*;
import java.net.URI;
import java.util.List;
import java.util.SortedMap;
+import java.util.SortedSet;
import static net.ripe.rpki.util.Streams.streamToSortedMap;
@@ -51,19 +42,25 @@ public class AspaEntity extends EntitySupport {
@Getter
private PublishedObject publishedObject;
+ @Getter
+ @Setter
+ @Column(name = "profile_version", nullable = false)
+ private Long profileVersion;
+
@Transient
private AspaCms cms;
- public AspaEntity(OutgoingResourceCertificate eeCertificate, AspaCms aspaCms, String filename, URI directory) {
+ public AspaEntity(OutgoingResourceCertificate eeCertificate, AspaCms aspaCms, String filename, URI directory, long profileVersion) {
super();
Validate.notNull(eeCertificate);
Validate.notNull(aspaCms);
this.certificate = eeCertificate;
this.publishedObject = new PublishedObject(
- eeCertificate.getSigningKeyPair(), filename, aspaCms.getEncoded(), true, directory, aspaCms.getValidityPeriod());
+ eeCertificate.getSigningKeyPair(), filename, aspaCms.getEncoded(), true, directory, aspaCms.getValidityPeriod(), aspaCms.getSigningTime());
+ this.profileVersion = profileVersion;
}
- public static SortedMap> entitiesToMaps(List entities) {
+ public static SortedMap> entitiesToMaps(List entities) {
return streamToSortedMap(
entities.stream(),
AspaEntity::getCustomerAsn,
@@ -106,11 +103,7 @@ public Asn getCustomerAsn() {
return getAspaCms().getCustomerAsn();
}
- public SortedMap getProviders() {
- return streamToSortedMap(
- getAspaCms().getProviderASSet().stream(),
- ProviderAS::getProviderAsn,
- providerAS -> AspaAfiLimit.fromOptionalAddressFamily(providerAS.getAfiLimit())
- );
+ public SortedSet getProviders() {
+ return ImmutableSortedSet.copyOf(getAspaCms().getProviderASSet());
}
}
diff --git a/src/main/java/net/ripe/rpki/domain/aspa/AspaEntityServiceBean.java b/src/main/java/net/ripe/rpki/domain/aspa/AspaEntityServiceBean.java
index a7d9563..893e1d7 100644
--- a/src/main/java/net/ripe/rpki/domain/aspa/AspaEntityServiceBean.java
+++ b/src/main/java/net/ripe/rpki/domain/aspa/AspaEntityServiceBean.java
@@ -10,7 +10,6 @@
import net.ripe.rpki.commons.crypto.ValidityPeriod;
import net.ripe.rpki.commons.crypto.cms.aspa.AspaCms;
import net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsBuilder;
-import net.ripe.rpki.commons.crypto.cms.aspa.ProviderAS;
import net.ripe.rpki.commons.crypto.rfc3779.ResourceExtension;
import net.ripe.rpki.commons.crypto.x509cert.CertificateInformationAccessUtil;
import net.ripe.rpki.commons.crypto.x509cert.X509CertificateInformationAccessDescriptor;
@@ -30,7 +29,6 @@
import net.ripe.rpki.domain.interca.CertificateIssuanceRequest;
import net.ripe.rpki.domain.naming.RepositoryObjectNamingStrategy;
import net.ripe.rpki.server.api.commands.CommandContext;
-import net.ripe.rpki.server.api.dto.AspaAfiLimit;
import net.ripe.rpki.server.api.services.command.UnparseableRpkiObjectException;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.DateTime;
@@ -50,6 +48,8 @@
@Service
@Slf4j
public class AspaEntityServiceBean implements AspaEntityService, CertificateAuthorityEventVisitor {
+ public static final long CURRENT_ASPA_PROFILE_VERSION = 16L;
+
private final CertificateAuthorityRepository certificateAuthorityRepository;
private final AspaConfigurationRepository aspaConfigurationRepository;
private final AspaEntityRepository aspaEntityRepository;
@@ -97,39 +97,44 @@ public void visitIncomingCertificateRevokedEvent(IncomingCertificateRevokedEvent
* are fully up-to-date with the configuration.
*/
public Pair, SortedMap> validateAspaConfiguration(ManagedCertificateAuthority ca) {
- SortedMap aspaEntities = aspaEntityRepository.findCurrentByCertificateAuthority(ca).stream()
- .collect(toSortedMap(AspaEntity::getCustomerAsn, x -> x));
+ // Not all ASPA entities can necessarily be parsed (e.g. profile change). While all ASNs should have 0..1 ASPA,
+ // there can be many unparesable ASPAs
+ List aspaEntities = aspaEntityRepository.findCurrentByCertificateAuthority(ca);
Optional maybeCurrentIncomingResourceCertificate = ca.findCurrentIncomingResourceCertificate();
if (!maybeCurrentIncomingResourceCertificate.isPresent()) {
// No current resource certificate, so all ASPA entities are invalid and without resources there is
// no applicable configuration
- return Pair.of(aspaEntities.values(), Collections.emptySortedMap());
+ return Pair.of(aspaEntities, Collections.emptySortedMap());
}
IncomingResourceCertificate incomingResourceCertificate = maybeCurrentIncomingResourceCertificate.get();
- Map> validatedAspaEntities = aspaEntities.values().stream()
- .collect(Collectors.partitioningBy(aspa -> isValidAspaEntity(incomingResourceCertificate, aspa)));
+ Map> validatedAspaEntities = aspaEntities.stream()
+ .collect(Collectors.partitioningBy(aspa -> isValidAspaEntity(incomingResourceCertificate, aspa)));
- SortedMap aspaConfiguration = aspaConfigurationRepository.findByCertificateAuthority(ca)
+ // Aspa Configurations covered by resource certificate
+ SortedMap aspaConfiguration = aspaConfigurationRepository.findConfigurationsWithProvidersByCertificateAuthority(ca)
.values()
.stream()
.filter(x -> incomingResourceCertificate.getCertifiedResources().contains(x.getCustomerAsn()))
.collect(toSortedMap(AspaConfiguration::getCustomerAsn, x -> x));
- SortedMapDifference> difference = Maps.difference(
+ SortedMapDifference> difference = Maps.difference(
AspaConfiguration.entitiesToMaps(aspaConfiguration),
AspaEntity.entitiesToMaps(validatedAspaEntities.get(true))
);
+ Map validAspaEntitiesByAsn = validatedAspaEntities.get(true)
+ .stream()
+ .collect(Collectors.toMap(AspaEntity::getCustomerAsn, x -> x));
+
List invalidAspaEntities = Stream.concat(
validatedAspaEntities.get(false).stream(),
Stream.concat(
difference.entriesOnlyOnRight().keySet().stream(),
difference.entriesDiffering().keySet().stream()
- )
- .map(aspaEntities::get)
+ ).map(validAspaEntitiesByAsn::get)
)
.collect(Collectors.toList());
@@ -153,24 +158,51 @@ public void updateAspaIfNeeded(ManagedCertificateAuthority ca) {
aspaEntity.revokeAndRemove(aspaEntityRepository);
}
for (AspaConfiguration aspaConfiguration : validated.getRight().values()) {
- AspaEntity aspaEntity = createAspaEntity(ca, aspaConfiguration);
- aspaEntityRepository.add(aspaEntity);
+ Optional aspaEntity = createAspaEntity(ca, aspaConfiguration);
+ aspaEntity.ifPresent(aspaEntityRepository::add);
}
}
private static boolean isValidAspaEntity(IncomingResourceCertificate incomingResourceCertificate, AspaEntity aspa) {
+ boolean isValidAndCurrent = false;
try {
- return aspa.getCertificate().isValid()
- && aspa.getCertificate().getSigningKeyPair().isCurrent()
- && Objects.equals(incomingResourceCertificate.getPublicationUri(), aspa.getAspaCms().getParentCertificateUri())
- && incomingResourceCertificate.getCertifiedResources().contains(aspa.getCustomerAsn());
+ isValidAndCurrent = aspa.getCertificate().isValid()
+ && aspa.getCertificate().getSigningKeyPair().isCurrent()
+ && aspa.getProfileVersion() == CURRENT_ASPA_PROFILE_VERSION
+ && Objects.equals(incomingResourceCertificate.getPublicationUri(), aspa.getAspaCms().getParentCertificateUri())
+ && incomingResourceCertificate.getCertifiedResources().contains(aspa.getCustomerAsn());
+
+ return isValidAndCurrent;
} catch (UnparseableRpkiObjectException e) {
return false;
+ } finally {
+ try {
+ if (!isValidAndCurrent && log.isInfoEnabled()) {
+ log.info("Will re-issue ASPA at {} certificate-valid={} keypair-current={} profile-version={} (current={}) parent-uri={} resources-match={}",
+ aspa.getCertificate().isValid(), aspa.getCertificate().getSigningKeyPair().isCurrent(), aspa.getProfileVersion(), CURRENT_ASPA_PROFILE_VERSION,
+ Objects.equals(incomingResourceCertificate.getPublicationUri(), aspa.getAspaCms().getParentCertificateUri()), incomingResourceCertificate.getCertifiedResources().contains(aspa.getCustomerAsn())
+ );
+ }
+ } catch (Exception e) {
+ // Ignore exceptions while printing debug message.
+ }
}
}
+ /**
+ * Create AspaEntity if possible
+ * @param certificateAuthority CA to issue under
+ * @param aspaConfiguration configuration to apply
+ * @return AspaEntity if possible, or empty.
+ */
@VisibleForTesting
- AspaEntity createAspaEntity(ManagedCertificateAuthority certificateAuthority, AspaConfiguration aspaConfiguration) {
+ Optional createAspaEntity(ManagedCertificateAuthority certificateAuthority, AspaConfiguration aspaConfiguration) {
+ // Filter out configurations that can not result in a valid ASPA entity, and would cause failures when trying
+ // to get the CMS payload
+ if (aspaConfiguration.getProviders().isEmpty() || !certificateAuthority.getCertifiedResources().contains(aspaConfiguration.getCustomerAsn())) {
+ return Optional.empty();
+ }
+
DateTime now = DateTime.now(DateTimeZone.UTC);
KeyPairEntity currentKeyPair = certificateAuthority.getCurrentKeyPair();
@@ -183,8 +215,8 @@ AspaEntity createAspaEntity(ManagedCertificateAuthority certificateAuthority, As
AspaCms aspaCms = generateAspaCms(aspaConfiguration, eeKeyPair, endEntityCertificate.getCertificate());
URI publicationDirectory = CertificateInformationAccessUtil.extractPublicationDirectory(
currentKeyPair.getCurrentIncomingCertificate().getSia());
- return new AspaEntity(endEntityCertificate, aspaCms,
- informationAccessStrategy.aspaFilename(endEntityCertificate), publicationDirectory);
+ return Optional.of(new AspaEntity(endEntityCertificate, aspaCms,
+ informationAccessStrategy.aspaFilename(endEntityCertificate), publicationDirectory, CURRENT_ASPA_PROFILE_VERSION));
}
private OutgoingResourceCertificate createEndEntityCertificate(
@@ -199,10 +231,8 @@ private AspaCms generateAspaCms(AspaConfiguration aspaConfiguration, KeyPair eeK
AspaCmsBuilder builder = new AspaCmsBuilder();
builder.withCertificate(endEntityX509ResourceCertificate);
builder.withCustomerAsn(aspaConfiguration.getCustomerAsn());
- builder.withProviderASSet(aspaConfiguration.getProviders().entrySet().stream()
- .map(entry -> new ProviderAS(entry.getKey(), entry.getValue().toOptionalAddressFamily()))
- .collect(Collectors.toList())
- );
+ builder.withProviderASSet(aspaConfiguration.getProviders());
+
builder.withSignatureProvider(singleUseKeyPairFactory.signatureProvider());
return builder.build(eeKeyPair.getPrivate());
}
diff --git a/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java b/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java
index ce069b4..bca65f6 100644
--- a/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java
+++ b/src/main/java/net/ripe/rpki/domain/crl/CrlEntity.java
@@ -139,7 +139,7 @@ public void update(ValidityPeriod validityPeriod, ResourceCertificateRepository
withdraw();
setPublishedObject(new PublishedObject(
- keyPair, keyPair.getCrlFilename(), encoded, true, keyPair.getCertificateRepositoryLocation(), validityPeriod));
+ keyPair, keyPair.getCrlFilename(), encoded, true, keyPair.getCertificateRepositoryLocation(), validityPeriod, builder.getThisUpdateTime()));
}
private X509CrlBuilder newCrlBuilderWithEntries(Collection revokedCertificates) {
diff --git a/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java b/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java
index f71c899..98f8837 100644
--- a/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java
+++ b/src/main/java/net/ripe/rpki/domain/manifest/ManifestEntity.java
@@ -154,7 +154,7 @@ public void update(OutgoingResourceCertificate eeCertificate,
ManifestCms manifestCms = buildManifestCms(entries, eeCertificateKeyPair, signatureProvider);
- publishedObject = new PublishedObject(keyPair, keyPair.getManifestFilename(), manifestCms.getEncoded(), false, keyPair.getCertificateRepositoryLocation(), manifestCms.getValidityPeriod());
+ publishedObject = new PublishedObject(keyPair, keyPair.getManifestFilename(), manifestCms.getEncoded(), false, keyPair.getCertificateRepositoryLocation(), manifestCms.getValidityPeriod(), manifestCms.getSigningTime());
this.nextNumber++;
}
diff --git a/src/main/java/net/ripe/rpki/domain/roa/RoaEntity.java b/src/main/java/net/ripe/rpki/domain/roa/RoaEntity.java
index 3d0aa40..f927669 100644
--- a/src/main/java/net/ripe/rpki/domain/roa/RoaEntity.java
+++ b/src/main/java/net/ripe/rpki/domain/roa/RoaEntity.java
@@ -59,7 +59,7 @@ public RoaEntity(OutgoingResourceCertificate eeCertificate, RoaCms roaCms, Strin
Validate.notNull(roaCms);
this.certificate = eeCertificate;
this.publishedObject = new PublishedObject(
- eeCertificate.getSigningKeyPair(), filename, roaCms.getEncoded(), true, directory, roaCms.getValidityPeriod());
+ eeCertificate.getSigningKeyPair(), filename, roaCms.getEncoded(), true, directory, roaCms.getValidityPeriod(), roaCms.getSigningTime());
}
@Transient
diff --git a/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java b/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java
index 102483a..1e4bf59 100644
--- a/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java
+++ b/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java
@@ -1,7 +1,9 @@
package net.ripe.rpki.offline.ra.service;
+import lombok.extern.slf4j.Slf4j;
import net.ripe.rpki.commons.crypto.CertificateRepositoryObject;
import net.ripe.rpki.commons.crypto.util.EncodedPublicKey;
+import net.ripe.rpki.commons.crypto.util.SignedObjectUtil;
import net.ripe.rpki.domain.*;
import net.ripe.rpki.domain.archive.KeyPairDeletionService;
import net.ripe.rpki.domain.interca.CertificateIssuanceResponse;
@@ -17,8 +19,8 @@
import net.ripe.rpki.commons.ta.domain.response.SigningResponse;
import net.ripe.rpki.commons.ta.domain.response.TaResponse;
import net.ripe.rpki.commons.ta.domain.response.TrustAnchorResponse;
-import net.ripe.rpki.util.PublishedObjectUtil;
import org.joda.time.DateTime;
+import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -41,6 +43,7 @@
* I.e. stuff like: - republishing the offline objects - notifying the
* production CA about new certs / revoked keys
*/
+@Slf4j
@Component
public class TrustAnchorResponseProcessor {
@@ -95,17 +98,27 @@ List applyChangeToPublishedObjects(Map result = new ArrayList<>();
final Map activeObjects = convertToMap(trustAnchorPublishedObjectRepository.findActiveObjects());
+ // Use the same time as fallback for all files in a request
+ var now = Instant.now();
objectsToPublish.forEach((uri, objectToPublish) -> {
+ Instant creationTime;
+ try {
+ creationTime = SignedObjectUtil.getFileCreationTime(uri, objectToPublish.getEncoded());
+ } catch (SignedObjectUtil.NoTimeParsedException e) {
+ log.error("Could not determine creation time for object: " + uri, e);
+ creationTime = now;
+ }
+
if (activeObjects.containsKey(uri)) {
final TrustAnchorPublishedObject publishedObject = activeObjects.remove(uri);
if (!objectsAreSame(publishedObject, objectToPublish, uri)) {
publishedObject.withdraw();
result.add(publishedObject);
- result.add(new TrustAnchorPublishedObject(uri, objectToPublish.getEncoded(), PublishedObjectUtil.getFileCreationTime(uri, objectToPublish.getEncoded())));
+ result.add(new TrustAnchorPublishedObject(uri, objectToPublish.getEncoded(), creationTime));
}
} else {
- result.add(new TrustAnchorPublishedObject(uri, objectToPublish.getEncoded(), PublishedObjectUtil.getFileCreationTime(uri, objectToPublish.getEncoded())));
+ result.add(new TrustAnchorPublishedObject(uri, objectToPublish.getEncoded(), creationTime));
}
});
withdrawObjects(activeObjects.values());
diff --git a/src/main/java/net/ripe/rpki/rest/exception/RestExceptionControllerAdvice.java b/src/main/java/net/ripe/rpki/rest/exception/RestExceptionControllerAdvice.java
index 936a0af..a414602 100644
--- a/src/main/java/net/ripe/rpki/rest/exception/RestExceptionControllerAdvice.java
+++ b/src/main/java/net/ripe/rpki/rest/exception/RestExceptionControllerAdvice.java
@@ -1,7 +1,7 @@
package net.ripe.rpki.rest.exception;
import com.google.common.collect.ImmutableMap;
-import net.ripe.rpki.server.api.services.command.DuplicateResourceException;
+import net.ripe.rpki.server.api.services.command.IllegalResourceException;
import net.ripe.rpki.server.api.services.command.EntityTagDoesNotMatchException;
import net.ripe.rpki.server.api.services.command.NotHolderOfResourcesException;
import net.ripe.rpki.server.api.services.command.PrivateAsnsUsedException;
@@ -50,7 +50,7 @@ public class RestExceptionControllerAdvice {
CaNameInvalidException.class,
NotHolderOfResourcesException.class,
PrivateAsnsUsedException.class,
- DuplicateResourceException.class,
+ IllegalResourceException.class,
ConstraintViolationException.class
})
public ResponseEntity