diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 31593af35..ae0bfa4d2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,7 +16,7 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
@@ -44,7 +44,7 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 46df41e77..81637be17 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,7 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
@@ -94,6 +94,6 @@ jobs:
run: mvn -B -U verify
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
diff --git a/README.md b/README.md
index 12112f459..040695cf1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# CZERTAINLY Core
-> This repository is part of the commercial open source project CZERTAINLY. You can find more information about the project at [CZERTAINLY](https://github.com/3KeyCompany/CZERTAINLY) repository, including the contribution guide.
+> This repository is part of the open source project CZERTAINLY. You can find more information about the project at [CZERTAINLY](https://github.com/3KeyCompany/CZERTAINLY) repository, including the contribution guide.
`Core` provides the basic functionality for the CZERTAINLY platform. It implements the logic for the certificate lifecycle management and handles all related tasks. You can think about it as a brain of the CZERTAINLY platform.
diff --git a/hooks/build b/hooks/build
old mode 100644
new mode 100755
index 0ef5f8487..4c8d2730e
--- a/hooks/build
+++ b/hooks/build
@@ -14,7 +14,12 @@ echo "PreBuild build"
docker build --build-arg SERVER_USERNAME=$SERVER_USERNAME --build-arg SERVER_PASSWORD=$SERVER_PASSWORD -f $DOCKERFILE_PATH-pre -t prebuild .
echo "MVN Build"
-docker run -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml clean package
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ echo "MacOS detected, using TESTCONTAINERS_HOST_OVERRIDE"
+ docker run -e TESTCONTAINERS_HOST_OVERRIDE=docker.for.mac.host.internal -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml clean package
+else
+ docker run -v /var/run/docker.sock:/var/run/docker.sock --name czertainlycont -i prebuild mvn -f /home/app/pom.xml clean package
+fi
echo "Starting czertainlycont"
docker start -d --restart=always czertainlycont
diff --git a/pom.xml b/pom.xml
index 737a32ba6..ba1a3088b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
core
- 2.10.0
+ 2.11.0
CZERTAINLY-Core
@@ -26,7 +26,7 @@
com.czertainly
interfaces
- 2.10.0
+ 2.11.0
@@ -164,7 +164,7 @@
com.microsoft.azure
msal4j
- 1.14.0
+ 1.14.2
org.apache.httpcomponents
diff --git a/settings.xml b/settings.xml
index 431b741ed..a29599d18 100644
--- a/settings.xml
+++ b/settings.xml
@@ -12,8 +12,8 @@
github
- central
- https://repo1.maven.org/maven2
+ ossrh-releases
+ https://s01.oss.sonatype.org/content/repositories/releases
github
diff --git a/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java b/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java
index d1531b8c6..c4bb0e190 100644
--- a/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java
+++ b/src/main/java/com/czertainly/core/api/ExceptionHandlingAdvice.java
@@ -14,11 +14,13 @@
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MissingRequestValueException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.net.ConnectException;
import java.security.cert.CertificateException;
@@ -78,6 +80,42 @@ public ErrorMessageDto handleHttpRequestMethodNotSupportedException(HttpRequestM
return ErrorMessageDto.getInstance(ex.getMessage());
}
+ /**
+ * Handler for {@link IllegalArgumentException}.
+ *
+ * @return
+ */
+ @ExceptionHandler(IllegalArgumentException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorMessageDto handleIllegalArgumentException(IllegalArgumentException ex) {
+ LOG.info("HTTP 400: {}", ex.getMessage());
+ return ErrorMessageDto.getInstance(ex.getMessage());
+ }
+
+ /**
+ * Handler for {@link MethodArgumentTypeMismatchException}.
+ *
+ * @return
+ */
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorMessageDto handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
+ LOG.info("HTTP 400: {}", ex.getMessage());
+ return ErrorMessageDto.getInstance(ex.getMessage());
+ }
+
+ /**
+ * Handler for {@link MissingRequestValueException}.
+ *
+ * @return
+ */
+ @ExceptionHandler(MissingRequestValueException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ErrorMessageDto handleMissingRequestValueException(MissingRequestValueException ex) {
+ LOG.info("HTTP 400: {}", ex.getMessage());
+ return ErrorMessageDto.getInstance(ex.getMessage());
+ }
+
/**
* Handler for {@link NotDeletableException}.
*
diff --git a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java
index f86a8fdd9..0d1ebc3d3 100644
--- a/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java
+++ b/src/main/java/com/czertainly/core/api/web/CertificateControllerImpl.java
@@ -22,6 +22,7 @@
import com.czertainly.core.service.CertificateService;
import com.czertainly.core.service.v2.ClientOperationService;
import com.czertainly.core.util.converter.CertificateFormatConverter;
+import com.czertainly.core.util.converter.CertificateFormatEncodingConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.WebDataBinder;
@@ -55,6 +56,7 @@ public class CertificateControllerImpl implements CertificateController {
@InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(CertificateFormat.class, new CertificateFormatConverter());
+ webdataBinder.registerCustomEditor(CertificateFormatEncoding.class, new CertificateFormatEncodingConverter());
}
@Override
@@ -68,6 +70,11 @@ public CertificateDetailDto getCertificate(@PathVariable String uuid)
return certificateService.getCertificate(SecuredUUID.fromString(uuid));
}
+ @Override
+ public CertificateDownloadResponseDto downloadCertificate(String uuid, CertificateFormat certificateFormat, CertificateFormatEncoding encoding) throws CertificateException, NotFoundException, IOException {
+ return certificateService.downloadCertificate(uuid, certificateFormat, encoding);
+ }
+
@Override
public void deleteCertificate(@PathVariable String uuid) throws NotFoundException {
certificateService.deleteCertificate(SecuredUUID.fromString(uuid));
@@ -153,8 +160,8 @@ public CertificateChainResponseDto getCertificateChain(String uuid, boolean with
}
@Override
- public CertificateChainDownloadResponseDto downloadCertificateChain(String uuid, CertificateFormat certificateFormat, boolean withEndCertificate) throws NotFoundException, CertificateException {
- return certificateService.downloadCertificateChain(SecuredUUID.fromString(uuid), certificateFormat, withEndCertificate);
+ public CertificateChainDownloadResponseDto downloadCertificateChain(String uuid, CertificateFormat certificateFormat, boolean withEndCertificate, CertificateFormatEncoding encoding) throws NotFoundException, CertificateException {
+ return certificateService.downloadCertificateChain(SecuredUUID.fromString(uuid), certificateFormat, withEndCertificate, encoding);
}
@Override
diff --git a/src/main/java/com/czertainly/core/api/web/RAProfileManagementControllerImpl.java b/src/main/java/com/czertainly/core/api/web/RAProfileManagementControllerImpl.java
index df382e228..599f0e42b 100644
--- a/src/main/java/com/czertainly/core/api/web/RAProfileManagementControllerImpl.java
+++ b/src/main/java/com/czertainly/core/api/web/RAProfileManagementControllerImpl.java
@@ -11,6 +11,7 @@
import com.czertainly.api.model.common.UuidDto;
import com.czertainly.api.model.common.attribute.v2.BaseAttribute;
import com.czertainly.api.model.core.auth.Resource;
+import com.czertainly.api.model.core.certificate.CertificateDetailDto;
import com.czertainly.api.model.core.raprofile.RaProfileDto;
import com.czertainly.core.auth.AuthEndpoint;
import com.czertainly.core.security.authz.SecuredParentUUID;
@@ -23,6 +24,8 @@
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
import java.util.List;
import java.util.Optional;
@@ -164,4 +167,10 @@ public void associateRAProfileWithApprovalProfile(String authorityUuid, String r
public void disassociateRAProfileFromApprovalProfile(String authorityUuid, String raProfileUuid, String approvalProfileUuid) throws NotFoundException {
raProfileService.disassociateApprovalProfile(authorityUuid, raProfileUuid, SecuredUUID.fromString(approvalProfileUuid));
}
+
+ @Override
+ public List getAuthorityCertificateChain(String authorityUuid, String raProfileUuid) throws ConnectorException {
+ return raProfileService.getAuthorityCertificateChain(SecuredParentUUID.fromString(authorityUuid), SecuredUUID.fromString(raProfileUuid));
+ }
+
}
diff --git a/src/main/java/com/czertainly/core/dao/entity/Certificate.java b/src/main/java/com/czertainly/core/dao/entity/Certificate.java
index 842e2cd01..fa016429c 100644
--- a/src/main/java/com/czertainly/core/dao/entity/Certificate.java
+++ b/src/main/java/com/czertainly/core/dao/entity/Certificate.java
@@ -182,6 +182,9 @@ public class Certificate extends UniquelyIdentifiedAndAudited implements Seriali
@Column(name = "revoke_attributes")
private String revokeAttributes;
+ @Column(name = "trusted_ca")
+ private Boolean trustedCa;
+
@Override
public CertificateDetailDto mapToDto() {
final CertificateDetailDto dto = new CertificateDetailDto();
@@ -210,6 +213,7 @@ public CertificateDetailDto mapToDto() {
dto.setValidationStatus(validationStatus);
dto.setCertificateType(certificateType);
dto.setOwner(owner);
+ dto.setTrustedCa(trustedCa);
if (issuerCertificateUuid != null) dto.setIssuerCertificateUuid(issuerCertificateUuid.toString());
if (ownerUuid != null) dto.setOwnerUuid(ownerUuid.toString());
@@ -293,6 +297,7 @@ public CertificateDto mapToListDto() {
dto.setValidationStatus(validationStatus);
dto.setFingerprint(fingerprint);
dto.setOwner(owner);
+ dto.setTrustedCa(trustedCa);
if (issuerCertificateUuid != null) dto.setIssuerCertificateUuid(issuerCertificateUuid.toString());
if (ownerUuid != null) dto.setOwnerUuid(ownerUuid.toString());
dto.setCertificateType(certificateType);
@@ -744,4 +749,12 @@ private String getIssuerCommonNameToDto() {
}
return null;
}
+
+ public void setTrustedCa(boolean trustedCa) {
+ this.trustedCa = trustedCa;
+ }
+
+ public Boolean getTrustedCa() {
+ return trustedCa;
+ }
}
diff --git a/src/main/java/com/czertainly/core/dao/entity/Crl.java b/src/main/java/com/czertainly/core/dao/entity/Crl.java
new file mode 100644
index 000000000..d1b785a00
--- /dev/null
+++ b/src/main/java/com/czertainly/core/dao/entity/Crl.java
@@ -0,0 +1,53 @@
+package com.czertainly.core.dao.entity;
+
+import com.fasterxml.jackson.annotation.JsonBackReference;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "crl")
+public class Crl extends UniquelyIdentified {
+ @Column(name = "ca_certificate_uuid")
+ private UUID caCertificateUuid;
+
+ @Column(name = "issuer_dn", nullable = false)
+ private String issuerDn;
+
+ @Column(name = "serial_number", nullable = false)
+ private String serialNumber;
+
+ @Column(name = "crl_issuer_dn", nullable = false)
+ private String crlIssuerDn;
+
+ @Column(name = "crl_number", nullable = false)
+ private String crlNumber;
+
+ @Column(name = "next_update", nullable = false)
+ private Date nextUpdate;
+
+ @Column(name = "crl_number_delta")
+ private String crlNumberDelta;
+
+ @Column(name = "next_update_delta")
+ private Date nextUpdateDelta;
+
+ @Column(name = "last_revocation_date")
+ private Date lastRevocationDate;
+
+ @OneToMany(mappedBy = "crl", fetch = FetchType.LAZY)
+ @JsonBackReference
+ private List crlEntries;
+
+ public Map getCrlEntriesMap() {
+ return crlEntries.stream().collect(Collectors.toMap(crlEntry -> crlEntry.getId().getSerialNumber(), crlEntry -> crlEntry));
+ }
+}
diff --git a/src/main/java/com/czertainly/core/dao/entity/CrlEntry.java b/src/main/java/com/czertainly/core/dao/entity/CrlEntry.java
new file mode 100644
index 000000000..8fe7b0dbc
--- /dev/null
+++ b/src/main/java/com/czertainly/core/dao/entity/CrlEntry.java
@@ -0,0 +1,64 @@
+package com.czertainly.core.dao.entity;
+
+import com.czertainly.api.model.core.authority.CertificateRevocationReason;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+import java.util.UUID;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "crl_entry")
+public class CrlEntry implements Serializable {
+
+ @EmbeddedId
+ private CrlEntryId id = new CrlEntryId();
+
+ @ManyToOne
+ @MapsId("crlUuid")
+ private Crl crl;
+
+ @Column(name = "revocation_date", nullable = false)
+ private Date revocationDate;
+
+ @Column(name = "revocation_reason", nullable = false)
+ @Enumerated(EnumType.STRING)
+ private CertificateRevocationReason revocationReason;
+
+ public UUID getCrlUuid() {
+ return crl.getUuid();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ CrlEntry that = (CrlEntry) o;
+ return Objects.equals(crl, that.crl) &&
+ Objects.equals(id.getSerialNumber(), that.getId().getSerialNumber());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getCrlUuid(), id.getSerialNumber());
+ }
+
+ @Override
+ public String toString() {
+ return "CertificateLocation{" +
+ "id=" + id +
+ ", revocationReason='" + revocationReason + '\'' +
+ ", revocationDate='" + revocationDate + '\'' +
+ '}';
+ }
+
+
+}
diff --git a/src/main/java/com/czertainly/core/dao/entity/CrlEntryId.java b/src/main/java/com/czertainly/core/dao/entity/CrlEntryId.java
new file mode 100644
index 000000000..b1db896be
--- /dev/null
+++ b/src/main/java/com/czertainly/core/dao/entity/CrlEntryId.java
@@ -0,0 +1,60 @@
+package com.czertainly.core.dao.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Embedded class for a composite primary key
+ */
+@Getter
+@Setter
+@Embeddable
+public class CrlEntryId implements Serializable {
+
+ @Column(name = "crl_uuid", nullable = false)
+ private UUID crlUuid;
+
+ @Column(name = "serial_number", nullable = false)
+ private String serialNumber;
+
+ public CrlEntryId() {
+ }
+
+ public CrlEntryId(UUID crlUuid, String serialNumber) {
+ this.crlUuid = crlUuid;
+ this.serialNumber = serialNumber;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ CrlEntryId that = (CrlEntryId) o;
+ return Objects.equals(crlUuid, that.crlUuid) &&
+ Objects.equals(serialNumber, that.serialNumber);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(crlUuid, serialNumber);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("crlUuid", crlUuid)
+ .append("serialNumber", serialNumber)
+ .toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/czertainly/core/dao/entity/RaProfile.java b/src/main/java/com/czertainly/core/dao/entity/RaProfile.java
index 8a40ec1a6..4f34b432b 100644
--- a/src/main/java/com/czertainly/core/dao/entity/RaProfile.java
+++ b/src/main/java/com/czertainly/core/dao/entity/RaProfile.java
@@ -54,6 +54,9 @@ public class RaProfile extends UniquelyIdentifiedAndAudited implements Serializa
@Column(name = "enabled")
private Boolean enabled;
+ @Column(name = "authority_certificate_uuid")
+ private UUID authorityCertificateUuid;
+
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
name = "ra_profile_2_compliance_profile",
@@ -274,6 +277,14 @@ public void setProtocolAttribute(RaProfileProtocolAttribute protocolAttribute) {
this.protocolAttribute = protocolAttribute;
}
+ public UUID getAuthorityCertificateUuid() {
+ return authorityCertificateUuid;
+ }
+
+ public void setAuthorityCertificateUuid(UUID authorityCertificateUuid) {
+ this.authorityCertificateUuid = authorityCertificateUuid;
+ }
+
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
diff --git a/src/main/java/com/czertainly/core/dao/repository/CertificateRepository.java b/src/main/java/com/czertainly/core/dao/repository/CertificateRepository.java
index 259279f42..7b7a4aeb5 100644
--- a/src/main/java/com/czertainly/core/dao/repository/CertificateRepository.java
+++ b/src/main/java/com/czertainly/core/dao/repository/CertificateRepository.java
@@ -30,8 +30,6 @@ public interface CertificateRepository extends SecurityFilterRepository findBySubjectDn(String subjectDn);
-
List findByRaProfile(RaProfile raProfile);
List findByGroup(Group group);
@@ -76,7 +74,7 @@ List findCertificatesToCheckStatus(@Param("statusValidityEndTimesta
List findByRaProfileAndComplianceStatusIsNotNull(RaProfile raProfile);
- Optional findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber);
+ Optional findBySubjectDnNormalizedAndSerialNumber(String subjectDnNormalized, String serialNumber);
Optional findByIssuerDnNormalizedAndSerialNumber(String issuerDnNormalized, String serialNumber);
diff --git a/src/main/java/com/czertainly/core/dao/repository/CrlEntryRepository.java b/src/main/java/com/czertainly/core/dao/repository/CrlEntryRepository.java
new file mode 100644
index 000000000..3a50e99e8
--- /dev/null
+++ b/src/main/java/com/czertainly/core/dao/repository/CrlEntryRepository.java
@@ -0,0 +1,14 @@
+package com.czertainly.core.dao.repository;
+
+import com.czertainly.core.dao.entity.CrlEntry;
+import com.czertainly.core.dao.entity.CrlEntryId;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface CrlEntryRepository extends SecurityFilterRepository
+{
+
+ Optional findById(CrlEntryId id);
+}
diff --git a/src/main/java/com/czertainly/core/dao/repository/CrlRepository.java b/src/main/java/com/czertainly/core/dao/repository/CrlRepository.java
new file mode 100644
index 000000000..756068292
--- /dev/null
+++ b/src/main/java/com/czertainly/core/dao/repository/CrlRepository.java
@@ -0,0 +1,14 @@
+package com.czertainly.core.dao.repository;
+
+import com.czertainly.core.dao.entity.Crl;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface CrlRepository extends SecurityFilterRepository
+{
+ Optional findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber);
+
+
+}
diff --git a/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java b/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java
index 12ff0ca01..fbcb5a59f 100644
--- a/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java
+++ b/src/main/java/com/czertainly/core/enums/SearchFieldNameEnum.java
@@ -55,7 +55,9 @@ public enum SearchFieldNameEnum {
LOCATION_INSTANCE_NAME(SearchableFields.ENTITY_INSTANCE_NAME, "Entity instance", SearchFieldTypeEnum.LIST),
LOCATION_ENABLED(SearchableFields.ENABLED, "Enabled", SearchFieldTypeEnum.BOOLEAN),
LOCATION_SUPPORT_MULTIPLE_ENTRIES(SearchableFields.SUPPORT_MULTIPLE_ENTRIES, "Support multiple entries", SearchFieldTypeEnum.BOOLEAN),
- LOCATION_SUPPORT_KEY_MANAGEMENT(SearchableFields.SUPPORT_KEY_MANAGEMENT, "Support key management", SearchFieldTypeEnum.BOOLEAN);
+ LOCATION_SUPPORT_KEY_MANAGEMENT(SearchableFields.SUPPORT_KEY_MANAGEMENT, "Support key management", SearchFieldTypeEnum.BOOLEAN),
+
+ TRUSTED_CA(SearchableFields.TRUSTED_CA, "Trusted CA", SearchFieldTypeEnum.BOOLEAN);
private SearchableFields fieldProperty;
diff --git a/src/main/java/com/czertainly/core/messaging/listeners/NotificationListener.java b/src/main/java/com/czertainly/core/messaging/listeners/NotificationListener.java
index 57405038a..adc84e23b 100644
--- a/src/main/java/com/czertainly/core/messaging/listeners/NotificationListener.java
+++ b/src/main/java/com/czertainly/core/messaging/listeners/NotificationListener.java
@@ -180,9 +180,14 @@ private void sendExternalNotifications(UUID notificationInstanceUUID, Notificati
UUID roleUuid = recipient.getRecipientUuid();
try {
RoleDetailDto roleDetailDto = roleManagementApiClient.getRoleDetail(roleUuid.toString());
- recipientDto = new NotificationRecipientDto();
- recipientDto.setEmail(roleDetailDto.getEmail());
- recipientDto.setName(roleDetailDto.getName());
+ String email = roleDetailDto.getEmail();
+ if (email == null || email.isBlank()) {
+ logger.warn("Role with UUID {} does not have specified email, notification was not sent for this role.", roleUuid);
+ } else {
+ recipientDto = new NotificationRecipientDto();
+ recipientDto.setEmail(email);
+ recipientDto.setName(roleDetailDto.getName());
+ }
recipientCustomAttributes = attributeService.getCustomAttributesWithValues(roleUuid, Resource.ROLE);
} catch (Exception e) {
@@ -193,9 +198,14 @@ private void sendExternalNotifications(UUID notificationInstanceUUID, Notificati
UUID groupUuid = recipient.getRecipientUuid();
Optional group = groupRepository.findByUuid(groupUuid);
if (group.isPresent()) {
- NotificationRecipientDto groupDto = new NotificationRecipientDto();
- groupDto.setName(group.get().getName());
- groupDto.setEmail(group.get().getEmail());
+ String email = group.get().getEmail();
+ if (email == null || email.isBlank()) {
+ logger.warn("Group with UUID {} does not have specified email, notification was not sent for this group.", groupUuid);
+ } else {
+ recipientDto = new NotificationRecipientDto();
+ recipientDto.setName(group.get().getName());
+ recipientDto.setEmail(email);
+ }
recipientCustomAttributes = attributeService.getCustomAttributesWithValues(groupUuid, Resource.GROUP);
} else {
diff --git a/src/main/java/com/czertainly/core/messaging/producers/NotificationProducer.java b/src/main/java/com/czertainly/core/messaging/producers/NotificationProducer.java
index 6bc2bbaf7..1b718cdff 100644
--- a/src/main/java/com/czertainly/core/messaging/producers/NotificationProducer.java
+++ b/src/main/java/com/czertainly/core/messaging/producers/NotificationProducer.java
@@ -63,7 +63,7 @@ public void produceNotificationCertificateStatusChanged(CertificateValidationSta
produceMessage(new NotificationMessage(NotificationType.CERTIFICATE_STATUS_CHANGED,
Resource.CERTIFICATE,
UUID.fromString(certificateDto.getUuid()),
- NotificationRecipient.buildUserOrGroupNotificationRecipient(UUID.fromString(certificateDto.getOwnerUuid()), certificateDto.getGroup() != null ? UUID.fromString(certificateDto.getGroup().getUuid()) : null),
+ NotificationRecipient.buildUserOrGroupNotificationRecipient(certificateDto.getOwnerUuid() != null ? UUID.fromString(certificateDto.getOwnerUuid()) : null, certificateDto.getGroup() != null ? UUID.fromString(certificateDto.getGroup().getUuid()) : null),
certificateDto.getRaProfile() == null ? new NotificationDataCertificateStatusChanged(oldStatus.getLabel(), newStatus.getLabel(), certificateDto.getUuid(), certificateDto.getFingerprint(), certificateDto.getSerialNumber(), certificateDto.getSubjectDn(), certificateDto.getIssuerDn())
: new NotificationDataCertificateStatusChanged(oldStatus.getLabel(), newStatus.getLabel(), certificateDto.getUuid(), certificateDto.getFingerprint(), certificateDto.getSerialNumber(), certificateDto.getSubjectDn(), certificateDto.getIssuerDn(), certificateDto.getRaProfile().getAuthorityInstanceUuid(), certificateDto.getRaProfile().getUuid(), certificateDto.getRaProfile().getName())));
}
diff --git a/src/main/java/com/czertainly/core/service/CertificateService.java b/src/main/java/com/czertainly/core/service/CertificateService.java
index 71e60924a..0e88a2dbb 100644
--- a/src/main/java/com/czertainly/core/service/CertificateService.java
+++ b/src/main/java/com/czertainly/core/service/CertificateService.java
@@ -51,7 +51,9 @@ public interface CertificateService extends ResourceExtensionService {
CertificateChainResponseDto getCertificateChain(SecuredUUID uuid, boolean withEndCertificate) throws NotFoundException;
- CertificateChainDownloadResponseDto downloadCertificateChain(SecuredUUID uuid, CertificateFormat certificateFormat, boolean withEndCertificate) throws NotFoundException, CertificateException;
+ CertificateChainDownloadResponseDto downloadCertificateChain(SecuredUUID uuid, CertificateFormat certificateFormat, boolean withEndCertificate, CertificateFormatEncoding encoding) throws NotFoundException, CertificateException;
+
+ CertificateDownloadResponseDto downloadCertificate(String uuid, CertificateFormat certificateFormat, CertificateFormatEncoding encoding) throws CertificateException, NotFoundException, IOException;
/**
* Function to get the validation result of the certificate
diff --git a/src/main/java/com/czertainly/core/service/CrlService.java b/src/main/java/com/czertainly/core/service/CrlService.java
new file mode 100644
index 000000000..a6ce7141a
--- /dev/null
+++ b/src/main/java/com/czertainly/core/service/CrlService.java
@@ -0,0 +1,19 @@
+package com.czertainly.core.service;
+
+import com.czertainly.core.dao.entity.Crl;
+import com.czertainly.core.dao.entity.CrlEntry;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.UUID;
+
+public interface CrlService {
+
+ Crl createCrlAndCrlEntries(byte[] crlDistributionPointsEncoded, String issuerDn, String issuerSerialNumber, UUID caCertificateUuid, String oldCrlNumber) throws IOException;
+
+ Crl updateCrlAndCrlEntriesFromDeltaCrl(X509Certificate certificate, Crl crl, String issuerDn, String issuerSerialNumber, UUID caCertificateUuid) throws IOException;
+
+ Crl getCurrentCrl(X509Certificate certificate, X509Certificate issuerCertificate) throws IOException;
+
+ CrlEntry findCrlEntryForCertificate(String serialNumber, UUID crlUuid);
+}
diff --git a/src/main/java/com/czertainly/core/service/RaProfileService.java b/src/main/java/com/czertainly/core/service/RaProfileService.java
index 646b519a5..158ca170c 100644
--- a/src/main/java/com/czertainly/core/service/RaProfileService.java
+++ b/src/main/java/com/czertainly/core/service/RaProfileService.java
@@ -9,6 +9,7 @@
import com.czertainly.api.model.client.compliance.SimplifiedComplianceProfileDto;
import com.czertainly.api.model.client.raprofile.*;
import com.czertainly.api.model.common.attribute.v2.BaseAttribute;
+import com.czertainly.api.model.core.certificate.CertificateDetailDto;
import com.czertainly.api.model.core.raprofile.RaProfileDto;
import com.czertainly.core.dao.entity.RaProfile;
import com.czertainly.core.security.authz.SecuredParentUUID;
@@ -16,6 +17,8 @@
import com.czertainly.core.security.authz.SecurityFilter;
import com.czertainly.core.service.model.SecuredList;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
import java.util.List;
import java.util.Optional;
@@ -121,4 +124,16 @@ public interface RaProfileService extends ResourceExtensionService {
ApprovalProfileRelationDto associateApprovalProfile(String authorityInstanceUuid, String raProfileUuid, SecuredUUID approvalProfileUuid) throws NotFoundException;
void disassociateApprovalProfile(String authorityInstanceUuid, String raProfileUuid, SecuredUUID approvalProfileUuid) throws NotFoundException;
+
+ /**
+ * Function to get the list of CA certificates associated with the RA Profile.
+ * Certificate chain is returned from the connector if the endpoint is implemented in the connector.
+ *
+ * @param authorityUuid UUID of the authority
+ * @param raProfileUuid UUID of the RA Profile
+ * @return List of CA Certificates
+ * @throws ConnectorException in case the connector throws an exception
+ */
+ List getAuthorityCertificateChain(SecuredParentUUID authorityUuid, SecuredUUID raProfileUuid)
+ throws ConnectorException;
}
diff --git a/src/main/java/com/czertainly/core/service/impl/AttributeServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/AttributeServiceImpl.java
index 31c269524..9120d267f 100644
--- a/src/main/java/com/czertainly/core/service/impl/AttributeServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/AttributeServiceImpl.java
@@ -41,6 +41,8 @@
import com.czertainly.core.util.AttributeDefinitionUtils;
import com.czertainly.core.util.SearchHelper;
import com.czertainly.core.util.converter.Sql2PredicateConverter;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
@@ -56,6 +58,8 @@
@Service
@Transactional
public class AttributeServiceImpl implements AttributeService {
+ private static final ObjectMapper ATTRIBUTES_OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
private static final Logger logger = LoggerFactory.getLogger(AttributeServiceImpl.class);
private final List CUSTOM_ATTRIBUTE_COMPLIANT_RESOURCES = List.of(
Resource.CERTIFICATE,
@@ -303,11 +307,27 @@ public void updateAttributeContent(UUID objectUuid, List at
@Override
public void updateAttributeContent(UUID objectUuid, UUID attributeUuid, List attributeContent, Resource resource) throws NotFoundException {
AttributeDefinition attributeDefinition = getAttributeDefinition(SecuredUUID.fromUUID(attributeUuid), AttributeType.CUSTOM);
- CustomAttributeProperties props = attributeDefinition.getAttributeDefinition(CustomAttribute.class).getProperties();
- if (props.isReadOnly()) {
- throw new ValidationException(ValidationError.create("Cannot update content for readonly attribute"));
+ CustomAttribute customAttribute = attributeDefinition.getAttributeDefinition(CustomAttribute.class);
+
+ // validate setting of readonly attribute when it is not removal
+ if (customAttribute.getProperties().isReadOnly() && attributeContent != null) {
+ // content has to have same amount of items (in case of lists)
+ if (attributeContent.size() != customAttribute.getContent().size()) {
+ throw new ValidationException(ValidationError.create("Cannot change content of readonly attribute"));
+ }
+
+ // convert to specific attribute content class based on content type and compare each item individually for equality
+ var clazz = AttributeContentType.getClass(customAttribute.getContentType());
+ for (int i = 0; i < attributeContent.size(); i++) {
+ BaseAttributeContent> attributeContentMapped = (BaseAttributeContent>) ATTRIBUTES_OBJECT_MAPPER.convertValue(attributeContent.get(i), clazz);
+ BaseAttributeContent> definitionContentMapped = (BaseAttributeContent>) ATTRIBUTES_OBJECT_MAPPER.convertValue(customAttribute.getContent().get(i), clazz);
+ if (!attributeContentMapped.equals(definitionContentMapped)) {
+ throw new ValidationException(ValidationError.create("Cannot change content of readonly attribute"));
+ }
+ }
}
+ // find existing content for this resource and attribute
List attributeContent2Objects = attributeContent2ObjectRepository
.findByObjectUuidAndObjectTypeAndAttributeContentAttributeDefinitionUuid(
objectUuid,
@@ -315,10 +335,13 @@ public void updateAttributeContent(UUID objectUuid, UUID attributeUuid, List getResourceSearchableFieldInformation(Resource resource) {
final List searchFieldDataByGroupDtos = new ArrayList<>();
- final List metadataSearchFieldObject = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource,List.of(AttributeType.META));
+ final List metadataSearchFieldObject = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource, List.of(AttributeType.META));
if (!metadataSearchFieldObject.isEmpty()) {
searchFieldDataByGroupDtos.add(new SearchFieldDataByGroupDto(SearchHelper.prepareSearchForJSON(metadataSearchFieldObject), SearchGroup.META));
}
- final List customAttrSearchFieldObject = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource,List.of(AttributeType.CUSTOM));
+ final List customAttrSearchFieldObject = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource, List.of(AttributeType.CUSTOM));
if (!customAttrSearchFieldObject.isEmpty()) {
searchFieldDataByGroupDtos.add(new SearchFieldDataByGroupDto(SearchHelper.prepareSearchForJSON(customAttrSearchFieldObject), SearchGroup.CUSTOM));
}
@@ -523,7 +546,7 @@ public List getResourceObjectUuidsByFilters(Resource resource, SecurityFil
return null;
}
- final List searchFieldObjects = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource,List.of(AttributeType.CUSTOM, AttributeType.META));
+ final List searchFieldObjects = attributeContentRepository.findDistinctAttributeContentNamesByAttrTypeAndObjType(resource, List.of(AttributeType.CUSTOM, AttributeType.META));
final Sql2PredicateConverter.CriteriaQueryDataObject criteriaQueryDataObject = Sql2PredicateConverter.prepareQueryToSearchIntoAttributes(searchFieldObjects, attributesFilters, entityManager.getCriteriaBuilder(), resource);
return attributeContent2ObjectRepository.findUsingSecurityFilterByCustomCriteriaQuery(securityFilter, criteriaQueryDataObject.getRoot(), criteriaQueryDataObject.getCriteriaQuery(), criteriaQueryDataObject.getPredicate());
}
diff --git a/src/main/java/com/czertainly/core/service/impl/CallbackServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CallbackServiceImpl.java
index e6aced4b8..6531985ac 100644
--- a/src/main/java/com/czertainly/core/service/impl/CallbackServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/CallbackServiceImpl.java
@@ -17,9 +17,7 @@
import com.czertainly.api.model.core.audit.ObjectType;
import com.czertainly.api.model.core.audit.OperationType;
import com.czertainly.api.model.core.auth.Resource;
-import com.czertainly.api.model.core.connector.ConnectorDto;
import com.czertainly.api.model.core.connector.FunctionGroupCode;
-import com.czertainly.api.model.core.entity.EntityInstanceDto;
import com.czertainly.core.aop.AuditLogged;
import com.czertainly.core.dao.entity.AuthorityInstanceReference;
import com.czertainly.core.dao.entity.Connector;
@@ -32,12 +30,12 @@
import com.czertainly.core.util.AttributeDefinitionUtils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import jakarta.transaction.Transactional;
import java.util.List;
import java.util.UUID;
@@ -77,9 +75,8 @@ public class CallbackServiceImpl implements CallbackService {
@AuditLogged(originator = ObjectType.FE, affected = ObjectType.ATTRIBUTES, operation = OperationType.CALLBACK)
public Object callback(String uuid, FunctionGroupCode functionGroup, String kind, RequestAttributeCallback callback) throws ConnectorException, ValidationException {
Connector connector = connectorService.getConnectorEntity(SecuredUUID.fromString(uuid));
- List definitions;
- definitions = attributeApiClient.listAttributeDefinitions(connector.mapToDto(), functionGroup, kind);
- AttributeCallback attributeCallback = getAttributeByName(callback.getName(), definitions);
+ List definitions = attributeApiClient.listAttributeDefinitions(connector.mapToDto(), functionGroup, kind);
+ AttributeCallback attributeCallback = getAttributeByName(callback.getName(), definitions, connector.getUuid());
AttributeDefinitionUtils.validateCallback(attributeCallback, callback);
if (attributeCallback.getCallbackContext().equals("core/getCredentials")) {
@@ -154,7 +151,7 @@ public Object resourceCallback(Resource resource, String resourceUuid, RequestAt
);
}
- AttributeCallback attributeCallback = getAttributeByName(callback.getName(), definitions);
+ AttributeCallback attributeCallback = getAttributeByName(callback.getName(), definitions, connector.getUuid());
AttributeDefinitionUtils.validateCallback(attributeCallback, callback);
if (attributeCallback.getCallbackContext().equals("core/getCredentials")) {
@@ -172,7 +169,7 @@ public Object resourceCallback(Resource resource, String resourceUuid, RequestAt
}
- private AttributeCallback getAttributeByName(String name, List attributes) throws NotFoundException {
+ private AttributeCallback getAttributeByName(String name, List attributes, UUID connectorUuid) throws NotFoundException {
for (BaseAttribute attributeDefinition : attributes) {
if (attributeDefinition.getName().equals(name)) {
switch (attributeDefinition.getType()) {
@@ -183,6 +180,13 @@ private AttributeCallback getAttributeByName(String name, List at
}
}
}
+
+ // if not present in definitions from connector, search in reference attributes in DB
+ DataAttribute referencedAttribute = attributeService.getReferenceAttribute(connectorUuid, name);
+ if (referencedAttribute != null) {
+ return referencedAttribute.getAttributeCallback();
+ }
+
throw new NotFoundException(BaseAttribute.class, name);
}
diff --git a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java
index 557c8fea4..0c99190aa 100644
--- a/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/CertificateServiceImpl.java
@@ -308,14 +308,25 @@ public void deleteCertificate(SecuredUUID uuid) throws NotFoundException {
public void updateCertificateObjects(SecuredUUID uuid, CertificateUpdateObjectsDto request) throws NotFoundException, CertificateOperationException {
logger.info("Updating certificate objects: RA {} group {} owner {}", request.getRaProfileUuid(), request.getGroupUuid(), request.getOwnerUuid());
if (request.getRaProfileUuid() != null) {
- switchRaProfile(uuid, SecuredUUID.fromString(request.getRaProfileUuid()));
+ switchRaProfile(uuid, request.getRaProfileUuid().isEmpty() ? null : SecuredUUID.fromString(request.getRaProfileUuid()));
}
if (request.getGroupUuid() != null) {
- updateCertificateGroup(uuid, SecuredUUID.fromString(request.getGroupUuid()));
+ updateCertificateGroup(uuid, request.getGroupUuid().isEmpty() ? null : SecuredUUID.fromString(request.getGroupUuid()));
}
if (request.getOwnerUuid() != null) {
- updateOwner(uuid, request.getOwnerUuid());
+ updateOwner(uuid, request.getOwnerUuid().isEmpty() ? null : request.getOwnerUuid(), null);
}
+ if (request.getTrustedCa() != null) {
+ updateTrustedCaMark(uuid, request.getTrustedCa());
+ }
+ }
+
+ private void updateTrustedCaMark(SecuredUUID uuid, Boolean trustedCa) throws NotFoundException {
+ Certificate certificate = getCertificateEntity(uuid);
+ if (certificate.getTrustedCa() == null) {
+ throw new ValidationException("Trying to mark certificate as trusted CA when certificate is not CA.");
+ }
+ certificate.setTrustedCa(trustedCa);
}
@Async
@@ -410,7 +421,8 @@ public List getSearchableFieldInformationByGroup() {
SearchHelper.prepareSearch(SearchFieldNameEnum.PUBLIC_KEY_ALGORITHM, new ArrayList<>(certificateRepository.findDistinctPublicKeyAlgorithm())),
SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_SIZE, new ArrayList<>(certificateRepository.findDistinctKeySize())),
SearchHelper.prepareSearch(SearchFieldNameEnum.KEY_USAGE, serializedListOfStringToListOfObject(certificateRepository.findDistinctKeyUsage())),
- SearchHelper.prepareSearch(SearchFieldNameEnum.PRIVATE_KEY)
+ SearchHelper.prepareSearch(SearchFieldNameEnum.PRIVATE_KEY),
+ SearchHelper.prepareSearch(SearchFieldNameEnum.TRUSTED_CA)
);
fields = new ArrayList<>(fields);
@@ -564,52 +576,92 @@ private Certificate constructCertificateChain(Certificate certificate, List certificateContent = getCertificateContent(List.of(uuid.toString()));
+ if (certificateContent.size() == 0) {
+ throw new ValidationException("Cannot download certificate chain, the end certificate is not issued.");
+ }
CertificateChainResponseDto certificateChainResponseDto = getCertificateChain(uuid, withEndCertificate);
List certificateChain = certificateChainResponseDto.getCertificates();
CertificateChainDownloadResponseDto certificateChainDownloadResponseDto = new CertificateChainDownloadResponseDto();
certificateChainDownloadResponseDto.setCompleteChain(certificateChainResponseDto.isCompleteChain());
certificateChainDownloadResponseDto.setFormat(certificateFormat);
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(new OutputStreamWriter(byteArrayOutputStream));
- List x509CertificateChain = new ArrayList<>();
- for (CertificateDto certificateDto : certificateChain) {
- Certificate certificateInstance;
- certificateInstance = getCertificateEntity(SecuredUUID.fromString(certificateDto.getUuid()));
- X509Certificate x509Certificate;
- try {
- x509Certificate = CertificateUtil.getX509Certificate(certificateInstance.getCertificateContent().getContent());
- } catch (CertificateException e) {
- certificateChainDownloadResponseDto.setCompleteChain(false);
- break;
+ certificateChainDownloadResponseDto.setEncoding(encoding);
+ certificateChainDownloadResponseDto.setContent(getDownloadedContent(certificateChain, certificateFormat, encoding, true));
+ return certificateChainDownloadResponseDto;
+ }
+
+ @Override
+ @ExternalAuthorization(resource = Resource.CERTIFICATE, action = ResourceAction.DETAIL)
+ public CertificateDownloadResponseDto downloadCertificate(String uuid, CertificateFormat certificateFormat, CertificateFormatEncoding encoding) throws CertificateException, NotFoundException, IOException {
+ CertificateDetailDto certificate = getCertificate(SecuredUUID.fromString(uuid));
+ if (certificate.getCertificateContent() == null) {
+ throw new ValidationException("Cannot download the certificate, certificate is not issued.");
+ }
+ CertificateDownloadResponseDto certificateDownloadResponseDto = new CertificateDownloadResponseDto();
+ certificateDownloadResponseDto.setFormat(certificateFormat);
+ certificateDownloadResponseDto.setEncoding(encoding);
+ certificateDownloadResponseDto.setContent(getDownloadedContent(List.of(certificate), certificateFormat, encoding, false));
+ return certificateDownloadResponseDto;
+ }
+
+ private String getDownloadedContent(List certificateDetailDtos, CertificateFormat certificateFormat, CertificateFormatEncoding encoding, boolean downloadingChain) throws NotFoundException, CertificateException {
+ if (certificateFormat == CertificateFormat.RAW) {
+ if (encoding == CertificateFormatEncoding.DER) {
+ if (downloadingChain) {
+ throw new ValidationException("DER encoding of raw format is unsupported for certificate chain.");
+ }
+ return getCertificateEntity(SecuredUUID.fromString(certificateDetailDtos.get(0).getUuid())).getCertificateContent().getContent();
}
- if (certificateFormat == CertificateFormat.PEM) {
+ // Encoding is PEM otherwise
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(new OutputStreamWriter(byteArrayOutputStream));
+ for (CertificateDto certificateDto : certificateDetailDtos) {
+ Certificate certificateInstance = getCertificateEntity(SecuredUUID.fromString(certificateDto.getUuid()));
+ String content = certificateInstance.getCertificateContent().getContent();
+ X509Certificate x509Certificate;
+ x509Certificate = CertificateUtil.getX509Certificate(content);
try {
jcaPEMWriter.writeObject(x509Certificate);
jcaPEMWriter.flush();
} catch (IOException e) {
- throw new CertificateException("Could not write certificate chain as PEM format: " + e.getMessage());
+ throw new CertificateException("Could not write downloaded content as PEM format: " + e.getMessage());
}
- } else {
+ }
+ return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
+ }
+ // Formatting is PKCS7 otherwise
+ else {
+ List x509CertificateChain = new ArrayList<>();
+ for (CertificateDto certificateDto : certificateDetailDtos) {
+ Certificate certificateInstance = getCertificateEntity(SecuredUUID.fromString(certificateDto.getUuid()));
+ X509Certificate x509Certificate;
+ x509Certificate = CertificateUtil.getX509Certificate(certificateInstance.getCertificateContent().getContent());
x509CertificateChain.add(x509Certificate);
}
- }
- if (certificateFormat == CertificateFormat.PKCS7) {
try {
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
generator.addCertificates(new JcaCertStore(x509CertificateChain));
byte[] encoded = generator.generate(new CMSProcessableByteArray(new byte[0])).getEncoded();
- ContentInfo contentInfo = ContentInfo.getInstance(ASN1Primitive.fromByteArray(encoded));
- jcaPEMWriter.writeObject(contentInfo);
- jcaPEMWriter.flush();
+ if (encoding == CertificateFormatEncoding.PEM) {
+ ContentInfo contentInfo = ContentInfo.getInstance(ASN1Primitive.fromByteArray(encoded));
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(new OutputStreamWriter(byteArrayOutputStream));
+ jcaPEMWriter.writeObject(contentInfo);
+ jcaPEMWriter.flush();
+ return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
+ }
+ // Encoding is DER otherwise
+ else {
+ return Base64.getEncoder().encodeToString(encoded);
+ }
} catch (Exception e) {
- throw new CertificateException("Could not write certificate chain as PKCS#7 format: " + e.getMessage());
+ throw new CertificateException("Could not write downloaded content as PKCS#7 format: " + e.getMessage());
}
}
- certificateChainDownloadResponseDto.setContent(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
- return certificateChainDownloadResponseDto;
}
+
@Override
public void validate(Certificate certificate) {
CertificateChainResponseDto certificateChainResponse = getCertificateChainInternal(certificate, true);
@@ -794,6 +846,7 @@ private CertificateContent checkAddCertificateContent(String fingerprint, String
@Override
@AuditLogged(originator = ObjectType.FE, affected = ObjectType.CERTIFICATE, operation = OperationType.CREATE)
+ @ExternalAuthorization(resource = Resource.CERTIFICATE, action = ResourceAction.CREATE)
public CertificateDetailDto upload(UploadCertificateRequestDto request) throws CertificateException, NoSuchAlgorithmException, AlreadyExistException {
X509Certificate certificate = CertificateUtil.parseUploadedCertificateContent(request.getCertificate());
String fingerprint = CertificateUtil.getThumbprint(certificate);
@@ -920,11 +973,10 @@ public int updateCertificatesStatusScheduled() {
int certificatesUpdated = 0;
logger.info(MarkerFactory.getMarker("scheduleInfo"), "Scheduled certificate status update. Batch size {}/{} certificates", certificates.size(), totalCertificates);
for (final Certificate certificate : certificates) {
- CertificateValidationStatus oldStatus = certificate.getValidationStatus();
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
validate(certificate);
- if (certificate.getRaProfileUuid() != null && certificate.getComplianceStatus() == null) {
+ if (certificate.getRaProfileUuid() != null && certificate.getComplianceStatus() == ComplianceStatus.NOT_CHECKED) {
complianceService.checkComplianceOfCertificate(certificate);
}
@@ -935,15 +987,6 @@ public int updateCertificatesStatusScheduled() {
continue;
}
- if (!oldStatus.equals(certificate.getValidationStatus())) {
- eventProducer.produceCertificateStatusChangeEventMessage(certificate.getUuid(), CertificateEvent.UPDATE_VALIDATION_STATUS, CertificateEventStatus.SUCCESS, oldStatus, certificate.getValidationStatus());
- try {
- notificationProducer.produceNotificationCertificateStatusChanged(oldStatus, certificate.getValidationStatus(), certificate.mapToListDto());
- } catch (Exception e) {
- logger.error("Sending certificate {} notification for change of status {} failed. Error: {}", certificate.getUuid(), certificate.getValidationStatus().getCode(), e.getMessage(), e);
- }
- }
-
++certificatesUpdated;
}
logger.info(MarkerFactory.getMarker("scheduleInfo"), "Certificates status updated for {}/{} certificates", certificatesUpdated, certificates.size());
@@ -1421,162 +1464,194 @@ private String downloadChain(String chainUrl) {
private void switchRaProfile(SecuredUUID uuid, SecuredUUID raProfileUuid) throws NotFoundException, CertificateOperationException {
Certificate certificate = getCertificateEntity(uuid);
- RaProfile newRaProfile = raProfileRepository.findByUuid(raProfileUuid)
- .orElseThrow(() -> new NotFoundException(RaProfile.class, raProfileUuid));
+ // check if there is change in RA profile compared to current state
+ if ((raProfileUuid == null && certificate.getRaProfileUuid() == null)
+ || (raProfileUuid != null && certificate.getRaProfileUuid() != null) && certificate.getRaProfileUuid().toString().equals(raProfileUuid.toString())) {
+ return;
+ }
+
+ // removing RA profile
+ RaProfile newRaProfile = null;
RaProfile currentRaProfile = certificate.getRaProfile();
- String currentRaProfileName = UNDEFINED_CERTIFICATE_OBJECT_NAME;
- if (currentRaProfile != null) {
- if (currentRaProfile.getUuid().toString().equals(raProfileUuid.toString())) {
- return;
+ String newRaProfileName = UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ String currentRaProfileName = currentRaProfile != null ? currentRaProfile.getName() : UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ CertificateIdentificationResponseDto response = null;
+ if (raProfileUuid != null) {
+ newRaProfile = raProfileRepository.findByUuid(raProfileUuid).orElseThrow(() -> new NotFoundException(RaProfile.class, raProfileUuid));
+ newRaProfileName = newRaProfile.getName();
+
+ // identify certificate by new authority
+ CertificateIdentificationRequestDto requestDto = new CertificateIdentificationRequestDto();
+ requestDto.setCertificate(certificate.getCertificateContent().getContent());
+ requestDto.setRaProfileAttributes(AttributeDefinitionUtils.getClientAttributes(newRaProfile.mapToDto().getAttributes()));
+ try {
+ response = certificateApiClient.identifyCertificate(
+ newRaProfile.getAuthorityInstanceReference().getConnector().mapToDto(),
+ newRaProfile.getAuthorityInstanceReference().getAuthorityInstanceUuid(),
+ requestDto);
+ } catch (ConnectorException e) {
+ certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate not identified by authority of new RA profile %s. Certificate needs to be reissued.", newRaProfile.getName()), "");
+ throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate not identified by authority of new RA profile %s. Certificate: %s", newRaProfile.getName(), certificate));
+ } catch (ValidationException e) {
+ certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate needs to be reissued.", newRaProfile.getName()), "");
+ throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate: %s", newRaProfile.getName(), certificate));
}
- currentRaProfileName = certificate.getRaProfile().getName();
}
- // identify certificate by new authority
- CertificateIdentificationResponseDto response;
- CertificateIdentificationRequestDto requestDto = new CertificateIdentificationRequestDto();
- requestDto.setCertificate(certificate.getCertificateContent().getContent());
- requestDto.setRaProfileAttributes(AttributeDefinitionUtils.getClientAttributes(newRaProfile.mapToDto().getAttributes()));
- try {
- response = certificateApiClient.identifyCertificate(
- newRaProfile.getAuthorityInstanceReference().getConnector().mapToDto(),
- newRaProfile.getAuthorityInstanceReference().getAuthorityInstanceUuid(),
- requestDto);
- } catch (ConnectorException e) {
- certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate not identified by authority of new RA profile %s. Certificate needs to be reissued.", newRaProfile.getName()), "");
- throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate not identified by authority of new RA profile %s. Certificate: %s", newRaProfile.getName(), certificate));
- } catch (ValidationException e) {
- certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.FAILED, String.format("Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate needs to be reissued.", newRaProfile.getName()), "");
- throw new CertificateOperationException(String.format("Cannot switch RA profile for certificate. Certificate identified by authority of new RA profile %s but not valid according to RA profile attributes. Certificate: %s", newRaProfile.getName(), certificate));
- }
+ certificate.setRaProfile(newRaProfile);
+ certificateRepository.save(certificate);
// delete old metadata
if (currentRaProfile != null) {
metadataService.deleteConnectorMetadata(currentRaProfile.getAuthorityInstanceReference().getConnectorUuid(), certificate.getUuid(), Resource.CERTIFICATE, null, null);
}
- // save metadata for identified certificate
- UUID connectorUuid = newRaProfile.getAuthorityInstanceReference().getConnectorUuid();
- metadataService.createMetadataDefinitions(connectorUuid, response.getMeta());
- metadataService.createMetadata(connectorUuid, certificate.getUuid(), null, null, response.getMeta(), Resource.CERTIFICATE, null);
+ // save metadata for identified certificate and run compliance
+ if (newRaProfile != null) {
+ UUID connectorUuid = newRaProfile.getAuthorityInstanceReference().getConnectorUuid();
+ metadataService.createMetadataDefinitions(connectorUuid, response.getMeta());
+ metadataService.createMetadata(connectorUuid, certificate.getUuid(), null, null, response.getMeta(), Resource.CERTIFICATE, null);
- certificate.setRaProfile(newRaProfile);
- certificateRepository.save(certificate);
- try {
- complianceService.checkComplianceOfCertificate(certificate);
- } catch (ConnectorException e) {
- logger.error("Error when checking compliance:", e);
+ try {
+ complianceService.checkComplianceOfCertificate(certificate);
+ } catch (ConnectorException e) {
+ logger.error("Error when checking compliance:", e);
+ }
}
- certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.SUCCESS, currentRaProfileName + " -> " + newRaProfile.getName(), "");
+
+ certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.SUCCESS, currentRaProfileName + " -> " + newRaProfileName, "");
}
private void updateCertificateGroup(SecuredUUID uuid, SecuredUUID groupUuid) throws NotFoundException {
Certificate certificate = getCertificateEntity(uuid);
- Group group = groupRepository.findByUuid(groupUuid)
- .orElseThrow(() -> new NotFoundException(Group.class, groupUuid));
- String originalGroup = UNDEFINED_CERTIFICATE_OBJECT_NAME;
- if (certificate.getGroup() != null) {
- originalGroup = certificate.getGroup().getName();
+ // check if there is change in group compared to current state
+ if ((groupUuid == null && certificate.getGroupUuid() == null)
+ || (groupUuid != null && certificate.getGroupUuid() != null) && certificate.getGroupUuid().toString().equals(groupUuid.toString())) {
+ return;
+ }
+
+ Group newGroup = null;
+ Group currentGroup = certificate.getGroup();
+ String newGroupName = UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ String currentGroupName = currentGroup != null ? currentGroup.getName() : UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ if (groupUuid != null) {
+ newGroup = groupRepository.findByUuid(groupUuid).orElseThrow(() -> new NotFoundException(Group.class, groupUuid));
+ newGroupName = newGroup.getName();
}
- certificate.setGroup(group);
+
+ certificate.setGroup(newGroup);
certificateRepository.save(certificate);
- certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_GROUP, CertificateEventStatus.SUCCESS, originalGroup + " -> " + group.getName(), "");
+ certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_GROUP, CertificateEventStatus.SUCCESS, currentGroupName + " -> " + newGroupName, "");
}
- private void updateOwner(SecuredUUID uuid, String ownerUuid) throws NotFoundException {
+ private void updateOwner(SecuredUUID uuid, String ownerUuid, String ownerName) throws NotFoundException {
Certificate certificate = getCertificateEntity(uuid);
// if there is no change, do not update and save request to Auth service
- if (certificate.getOwnerUuid() != null && certificate.getOwnerUuid().toString().equals(ownerUuid)) return;
+ if ((ownerUuid == null && certificate.getOwnerUuid() == null)
+ || (ownerUuid != null && certificate.getOwnerUuid() != null) && certificate.getOwnerUuid().toString().equals(ownerUuid)) {
+ return;
+ }
- String originalOwner = certificate.getOwner();
- if (originalOwner == null || originalOwner.isEmpty()) {
- originalOwner = UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ UUID newOwnerUuid = null;
+ String newOwnerName = ownerName;
+ String currentOwnerName = certificate.getOwner();
+ if (currentOwnerName == null || currentOwnerName.isEmpty()) {
+ currentOwnerName = UNDEFINED_CERTIFICATE_OBJECT_NAME;
+ }
+ if (ownerUuid != null) {
+ newOwnerUuid = UUID.fromString(ownerUuid);
+ if (ownerName == null) {
+ UserDetailDto userDetail = userManagementApiClient.getUserDetail(ownerUuid);
+ newOwnerName = userDetail.getUsername();
+ }
}
- UserDetailDto userDetail = userManagementApiClient.getUserDetail(ownerUuid);
- certificate.setOwner(userDetail.getUsername());
- certificate.setOwnerUuid(UUID.fromString(userDetail.getUuid()));
+
+ certificate.setOwner(newOwnerName);
+ certificate.setOwnerUuid(newOwnerUuid);
certificateRepository.save(certificate);
- certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, originalOwner + " -> " + userDetail.getUsername(), "");
+ certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, String.format("%s -> %s", currentOwnerName, newOwnerName == null ? UNDEFINED_CERTIFICATE_OBJECT_NAME : newOwnerName), "");
}
private void bulkUpdateRaProfile(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException {
- List batchHistoryOperationList = new ArrayList<>();
- RaProfile raProfile = raProfileRepository.findByUuid(SecuredUUID.fromString(request.getRaProfileUuid()))
- .orElseThrow(() -> new NotFoundException(RaProfile.class, request.getRaProfileUuid()));
+ boolean removeRaProfile = request.getRaProfileUuid().isEmpty();
if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) {
- for (String certificateUuid : request.getCertificateUuids()) {
+ for (String certificateUuidString : request.getCertificateUuids()) {
try {
- permissionEvaluator.certificate(SecuredUUID.fromString(certificateUuid));
- switchRaProfile(SecuredUUID.fromString(certificateUuid), SecuredUUID.fromString(request.getRaProfileUuid()));
+ SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString);
+ permissionEvaluator.certificate(certificateUuid);
+ switchRaProfile(certificateUuid, removeRaProfile ? null : SecuredUUID.fromString(request.getRaProfileUuid()));
} catch (CertificateOperationException e) {
logger.warn(e.getMessage());
}
}
} else {
+ RaProfile raProfile = removeRaProfile ? null : raProfileRepository.findByUuid(SecuredUUID.fromString(request.getRaProfileUuid()))
+ .orElseThrow(() -> new NotFoundException(RaProfile.class, request.getRaProfileUuid()));
+
String data = searchService.createCriteriaBuilderString(filter, false);
- if (!data.equals("")) {
+ if (!data.isEmpty()) {
data = "WHERE " + data;
}
- String profileUpdateQuery = "UPDATE Certificate c SET c.raProfile = " + raProfile.getUuid() + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
+
+ String profileUpdateQuery = "UPDATE Certificate c SET c.raProfile = " + (removeRaProfile ? "NULL" : raProfile.getUuid()) + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
certificateRepository.bulkUpdateQuery(profileUpdateQuery);
- certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.SUCCESS, "RA Profile Name: " + raProfile.getName());
+ certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_RA_PROFILE, CertificateEventStatus.SUCCESS, "RA Profile Name: " + (removeRaProfile ? UNDEFINED_CERTIFICATE_OBJECT_NAME : raProfile.getName()));
bulkUpdateRaProfileComplianceCheck(request.getFilters());
}
}
private void bulkUpdateCertificateGroup(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException {
- Group group = groupRepository.findByUuid(SecuredUUID.fromString(request.getGroupUuid()))
- .orElseThrow(() -> new NotFoundException(Group.class, request.getGroupUuid()));
- List batchHistoryOperationList = new ArrayList<>();
+ boolean removeGroup = request.getGroupUuid().isEmpty();
if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) {
- List batchOperationList = new ArrayList<>();
-
- for (String certificateUuid : request.getCertificateUuids()) {
- Certificate certificate = getCertificateEntity(SecuredUUID.fromString(certificateUuid));
- permissionEvaluator.certificate(certificate.getSecuredUuid());
- batchHistoryOperationList.add(certificateEventHistoryService.getEventHistory(CertificateEvent.UPDATE_GROUP, CertificateEventStatus.SUCCESS, certificate.getGroup() != null ? certificate.getGroup().getName() : UNDEFINED_CERTIFICATE_OBJECT_NAME + " -> " + group.getName(), "", certificate));
- certificate.setGroup(group);
- batchOperationList.add(certificate);
+ for (String certificateUuidString : request.getCertificateUuids()) {
+ SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString);
+ permissionEvaluator.certificate(certificateUuid);
+ updateCertificateGroup(certificateUuid, removeGroup ? null : SecuredUUID.fromString(request.getGroupUuid()));
}
- certificateRepository.saveAll(batchOperationList);
- certificateEventHistoryService.asyncSaveAllInBatch(batchHistoryOperationList);
} else {
+ Group group = groupRepository.findByUuid(SecuredUUID.fromString(request.getGroupUuid()))
+ .orElseThrow(() -> new NotFoundException(Group.class, request.getGroupUuid()));
+
String data = searchService.createCriteriaBuilderString(filter, false);
- if (!data.equals("")) {
+ if (!data.isEmpty()) {
data = "WHERE " + data;
}
- String groupUpdateQuery = "UPDATE Certificate c SET c.group = " + group.getUuid() + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
+ String groupUpdateQuery = "UPDATE Certificate c SET c.group = " + (removeGroup ? "NULL" : group.getUuid()) + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
certificateRepository.bulkUpdateQuery(groupUpdateQuery);
- certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_GROUP, CertificateEventStatus.SUCCESS, "Group Name: " + group.getName());
+ certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_GROUP, CertificateEventStatus.SUCCESS, "Group Name: " + (removeGroup ? UNDEFINED_CERTIFICATE_OBJECT_NAME : group.getName()));
}
}
private void bulkUpdateOwner(SecurityFilter filter, MultipleCertificateObjectUpdateDto request) throws NotFoundException {
- UserDetailDto userDetail = userManagementApiClient.getUserDetail(request.getOwnerUuid());
+ boolean removeOwner = request.getOwnerUuid().isEmpty();
+ String ownerUuid = null;
+ String ownerName = null;
+ if (!removeOwner) {
+ UserDetailDto userDetail = userManagementApiClient.getUserDetail(request.getOwnerUuid());
+ ownerUuid = request.getOwnerUuid();
+ ownerName = userDetail.getUsername();
+ }
List batchHistoryOperationList = new ArrayList<>();
if (request.getFilters() == null || request.getFilters().isEmpty() || (request.getCertificateUuids() != null && !request.getCertificateUuids().isEmpty())) {
List batchOperationList = new ArrayList<>();
- for (String certificateUuid : request.getCertificateUuids()) {
- Certificate certificate = getCertificateEntity(SecuredUUID.fromString(certificateUuid));
- permissionEvaluator.certificate(certificate.getSecuredUuid());
- batchHistoryOperationList.add(certificateEventHistoryService.getEventHistory(CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, certificate.getOwner() + " -> " + request.getOwnerUuid(), "", certificate));
- certificate.setOwner(userDetail.getUsername());
- certificate.setOwnerUuid(UUID.fromString(userDetail.getUuid()));
- batchOperationList.add(certificate);
+ for (String certificateUuidString : request.getCertificateUuids()) {
+ SecuredUUID certificateUuid = SecuredUUID.fromString(certificateUuidString);
+ permissionEvaluator.certificate(certificateUuid);
+ updateOwner(certificateUuid, ownerUuid, ownerName);
}
certificateRepository.saveAll(batchOperationList);
certificateEventHistoryService.asyncSaveAllInBatch(batchHistoryOperationList);
} else {
String data = searchService.createCriteriaBuilderString(filter, false);
- if (!data.equals("")) {
+ if (!data.isEmpty()) {
data = "WHERE " + data;
}
- String ownerUpdateQuery = "UPDATE Certificate c SET c.owner = '" + userDetail.getUsername() + "',c.owner_uuid = '" + UUID.fromString(userDetail.getUuid()) + "' " + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
+ String ownerUpdateQuery = "UPDATE Certificate c SET c.owner = '" + (removeOwner ? "NULL" : ownerName) + "',c.owner_uuid = '" + (removeOwner ? "NULL" : UUID.fromString(request.getOwnerUuid())) + "' " + searchService.getCompleteSearchQuery(request.getFilters(), "certificate", data, getSearchableFieldInformation(), true, false).replace("GROUP BY c.id ORDER BY c.id DESC", "");
certificateRepository.bulkUpdateQuery(ownerUpdateQuery);
- certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, "Owner: " + userDetail.getUsername());
+ certificateEventHistoryService.addEventHistoryForRequest(request.getFilters(), "Certificate", getSearchableFieldInformation(), CertificateEvent.UPDATE_OWNER, CertificateEventStatus.SUCCESS, "Owner: " + (removeOwner ? UNDEFINED_CERTIFICATE_OBJECT_NAME : ownerName));
}
}
diff --git a/src/main/java/com/czertainly/core/service/impl/ClientOperationServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/ClientOperationServiceImpl.java
index 78205552a..8b789c2f7 100644
--- a/src/main/java/com/czertainly/core/service/impl/ClientOperationServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/ClientOperationServiceImpl.java
@@ -177,6 +177,7 @@ public void editEndEntity(String raProfileName, String username, ClientEditEndEn
caRequest.setSubjectDN(request.getSubjectDN());
caRequest.setSubjectAltName(request.getSubjectAltName());
caRequest.setExtensionData(request.getExtensionData());
+ caRequest.setStatus(request.getStatus());
caRequest.setRaProfile(raProfile.mapToDto());
endEntityApiClient.updateEndEntity(
diff --git a/src/main/java/com/czertainly/core/service/impl/CrlServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CrlServiceImpl.java
new file mode 100644
index 000000000..5c57c0cdf
--- /dev/null
+++ b/src/main/java/com/czertainly/core/service/impl/CrlServiceImpl.java
@@ -0,0 +1,226 @@
+package com.czertainly.core.service.impl;
+
+import com.czertainly.api.exception.ValidationException;
+import com.czertainly.api.model.core.authority.CertificateRevocationReason;
+import com.czertainly.core.dao.entity.Certificate;
+import com.czertainly.core.dao.entity.Crl;
+import com.czertainly.core.dao.entity.CrlEntry;
+import com.czertainly.core.dao.entity.CrlEntryId;
+import com.czertainly.core.dao.repository.CertificateRepository;
+import com.czertainly.core.dao.repository.CrlEntryRepository;
+import com.czertainly.core.dao.repository.CrlRepository;
+import com.czertainly.core.service.CrlService;
+import com.czertainly.core.util.CrlUtil;
+import com.czertainly.core.util.CzertainlyX500NameStyle;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.security.cert.*;
+import java.util.*;
+
+@Service
+public class CrlServiceImpl implements CrlService {
+
+ private CertificateRepository certificateRepository;
+
+ private CrlRepository crlRepository;
+
+ private CrlEntryRepository crlEntryRepository;
+
+ @Autowired
+ public void setCertificateRepository(CertificateRepository certificateRepository) {
+ this.certificateRepository = certificateRepository;
+ }
+
+ @Autowired
+ public void setCrlRepository(CrlRepository crlRepository) {
+ this.crlRepository = crlRepository;
+ }
+
+ @Autowired
+ public void setCrlEntryRepository(CrlEntryRepository crlEntryRepository) {
+ this.crlEntryRepository = crlEntryRepository;
+ }
+
+ @Override
+ public Crl createCrlAndCrlEntries(byte[] crlDistributionPointsEncoded, String issuerDn, String issuerSerialNumber, UUID caCertificateUuid, String oldCrlNumber) throws IOException {
+ List crlUrls = CrlUtil.getCDPFromCertificate(crlDistributionPointsEncoded);
+
+ Crl crl = null;
+
+ for (String crlUrl : crlUrls) {
+ X509CRL X509Crl;
+ try {
+ X509Crl = CrlUtil.getX509Crl(crlUrl);
+ } catch (Exception e) {
+ // Failed to read content from URL, continue to next URL
+ continue;
+ }
+
+ String crlNumber = JcaX509ExtensionUtils.parseExtensionValue(X509Crl.getExtensionValue(Extension.cRLNumber.getId())).toString();
+ if (Objects.equals(crlNumber, oldCrlNumber)) return null;
+
+ crl = new Crl();
+ List crlEntries = new ArrayList<>();
+ crl.setNextUpdate(X509Crl.getNextUpdate());
+ byte[] issuerDnPrincipalEncoded = X509Crl.getIssuerX500Principal().getEncoded();
+ crl.setCrlIssuerDn(X500Name.getInstance(CzertainlyX500NameStyle.NORMALIZED, issuerDnPrincipalEncoded).toString());
+ crl.setSerialNumber(issuerSerialNumber);
+ crl.setIssuerDn(issuerDn);
+ crl.setCaCertificateUuid(caCertificateUuid);
+ crl.setCrlNumber(crlNumber);
+ crl.setCrlEntries(crlEntries);
+ crlRepository.save(crl);
+
+ Set extends X509CRLEntry> crlCertificates = X509Crl.getRevokedCertificates();
+ if (crlCertificates != null) {
+ Date lastRevocationDate = new Date(0);
+ for (X509CRLEntry x509CRLEntry : crlCertificates) {
+ CrlEntry crlEntry = createCrlEntry(x509CRLEntry, crl);
+ crlEntries.add(crlEntry);
+ if (crlEntry.getRevocationDate().after(lastRevocationDate))
+ lastRevocationDate = crlEntry.getRevocationDate();
+ }
+ crl.setLastRevocationDate(lastRevocationDate);
+ crlRepository.save(crl);
+ }
+
+ // Managed to process a CRL url and do not need to try other URLs
+ break;
+ }
+ return crl;
+ }
+
+ @Override
+ public Crl updateCrlAndCrlEntriesFromDeltaCrl(X509Certificate certificate, Crl crl, String issuerDn, String issuerSerialNumber, UUID caCertificateUuid) throws IOException {
+ List deltaCrlUrls = CrlUtil.getCDPFromCertificate(certificate.getExtensionValue(Extension.freshestCRL.getId()));
+ for (String deltaCrlUrl : deltaCrlUrls) {
+ X509CRL deltaCrl;
+ try {
+ deltaCrl = CrlUtil.getX509Crl(deltaCrlUrl);
+ } catch (Exception e) {
+ // Failed to read content from URL, continue to next URL
+ continue;
+ }
+ String deltaCrlIssuer = X500Name.getInstance(CzertainlyX500NameStyle.NORMALIZED, deltaCrl.getIssuerX500Principal().getEncoded()).toString();
+ // Compare CRL issuer with issuer stored in CRL entity, delta CRL is invalid if they are not the same
+ if (!Objects.equals(deltaCrlIssuer, crl.getCrlIssuerDn()))
+ throw new ValidationException("Delta CRL issuer not same as issuer stored in CRL entity");
+
+ // Compare DeltaCRLIndicator with base CRL number, if they are not equal, try to get newer CRL
+ String deltaCrlIndicator = JcaX509ExtensionUtils.parseExtensionValue(deltaCrl.getExtensionValue(Extension.deltaCRLIndicator.getId())).toString();
+ if (!Objects.equals(deltaCrlIndicator, crl.getCrlNumber())) {
+ Crl newCrl = createCrlAndCrlEntries(certificate.getExtensionValue(Extension.cRLDistributionPoints.getId()), issuerDn, issuerSerialNumber, caCertificateUuid, crl.getCrlNumber());
+ // If received CRL is null, it means it is the old one again, and we are not able to set delta CRL properly
+ if (newCrl == null)
+ throw new ValidationException("Unable to get CRL with base CRL number equal to DeltaCRLIndicator");
+ // Otherwise delete the old CRL and continue with the new CRL
+ crlRepository.delete(crl);
+ crl = newCrl;
+ }
+ updateDeltaCrl(crl, deltaCrl);
+ // Managed to process a delta CRL url and do not need to try other URLs
+ break;
+ }
+ return crl;
+ }
+
+
+ @Override
+ public Crl getCurrentCrl(X509Certificate certificate, X509Certificate issuerCertificate) throws IOException {
+ byte[] issuerDnPrincipalEncoded = certificate.getIssuerX500Principal().getEncoded();
+ String issuerDn = X500Name.getInstance(CzertainlyX500NameStyle.NORMALIZED, issuerDnPrincipalEncoded).toString();
+ String issuerSerialNumber = issuerCertificate.getSerialNumber().toString(16);
+ Optional crlOptional = crlRepository.findByIssuerDnAndSerialNumber(issuerDn, issuerSerialNumber);
+ Optional caCertificate = certificateRepository.findBySubjectDnNormalizedAndSerialNumber(issuerDn, issuerSerialNumber);
+ byte[] crlDistributionPoints = certificate.getExtensionValue(Extension.cRLDistributionPoints.getId());
+
+ Crl crl = null;
+ UUID caCertificateUuid = caCertificate.isPresent() ? caCertificate.get().getUuid() : null;
+ // If CRL is not present or current UTC time is past its next_update timestamp, download the CRL and save the CRL and its entries in database
+ if (crlOptional.isEmpty() || (crl = crlOptional.get()).getNextUpdate().before(new Date())) {
+ Crl newCrl = createCrlAndCrlEntries(crlDistributionPoints, issuerDn, issuerSerialNumber, caCertificateUuid, crl != null ? crl.getCrlNumber() : null);
+ // If CRL received is not null, then the downloaded CRL is updated CRL, delete old CRL and use updated one
+ if (newCrl != null) {
+ if (crlOptional.isPresent()) crlRepository.delete(crl);
+ crl = newCrl;
+ }
+ }
+
+ // Check if certificate has freshestCrl extension set
+ if (certificate.getExtensionValue(Extension.freshestCRL.getId()) != null) {
+ // If no delta CRL is set or delta CRL is not up-to-date, download delta CRL
+ if (crl != null && (crl.getNextUpdateDelta() == null || !crl.getNextUpdateDelta().before(new Date()))) {
+ updateCrlAndCrlEntriesFromDeltaCrl(certificate, crl, issuerDn, issuerSerialNumber, caCertificateUuid);
+ }
+ }
+ return crl;
+ }
+
+ @Override
+ public CrlEntry findCrlEntryForCertificate(String serialNumber, UUID crlUuid) {
+ CrlEntryId crlEntryId = new CrlEntryId(crlUuid, serialNumber);
+ return crlEntryRepository.findById(crlEntryId).orElse(null);
+ }
+
+ private void updateDeltaCrl(Crl crl, X509CRL deltaCrl) throws IOException {
+ ASN1Primitive encodedCrlNumber = JcaX509ExtensionUtils.parseExtensionValue(deltaCrl.getExtensionValue(Extension.cRLNumber.getId()));
+ // If delta CRL number has been set, check if delta CRL number is greater than one in DB entity, if it is, process delta CRL entries
+ if (crl.getCrlNumberDelta() == null || Integer.parseInt(encodedCrlNumber.toString()) > Integer.parseInt(crl.getCrlNumberDelta())) {
+ List crlEntries = crl.getCrlEntries();
+ Date lastRevocationDateNew = crl.getLastRevocationDate();
+ Set extends X509CRLEntry> deltaCrlEntries = deltaCrl.getRevokedCertificates();
+ if (deltaCrlEntries != null) {
+ Map crlEntryMap = crl.getCrlEntriesMap();
+ for (X509CRLEntry deltaCrlEntry : deltaCrlEntries) {
+ Date entryRevocationDate = deltaCrlEntry.getRevocationDate();
+ // Process only entries which revocation date is >= last_revocation_date, others are already in DB
+ if (entryRevocationDate.after(crl.getLastRevocationDate()) || entryRevocationDate.equals(crl.getLastRevocationDate())) {
+ String serialNumber = String.valueOf(deltaCrlEntry.getSerialNumber());
+ CrlEntry crlEntry = crlEntryMap.get(serialNumber);
+ // Entry by serial number is not present, add new one
+ if (crlEntry == null) {
+ CrlEntry crlEntryNew = createCrlEntry(deltaCrlEntry, crl);
+ crlEntries.add(crlEntryNew);
+ // Entry by serial number is present and revocation reason is REMOVE_FROM_CRL, remove this entry
+ } else if (Objects.equals(deltaCrlEntry.getRevocationReason(), CRLReason.REMOVE_FROM_CRL)) {
+ crlEntries.remove(crlEntry);
+ crlEntryRepository.delete(crlEntry);
+ // Entry by serial number is present, probably reason changed so update its revocation reason and date
+ } else {
+ crlEntry.setRevocationReason(deltaCrlEntry.getRevocationReason() == null ? CertificateRevocationReason.UNSPECIFIED : CertificateRevocationReason.fromCrlReason(deltaCrlEntry.getRevocationReason()));
+ crlEntry.setRevocationDate(deltaCrlEntry.getRevocationDate());
+ crlEntryRepository.save(crlEntry);
+ }
+ if (lastRevocationDateNew.before(deltaCrlEntry.getRevocationDate()))
+ lastRevocationDateNew = deltaCrlEntry.getRevocationDate();
+ }
+ }
+ }
+ // Update last revocation date from new/updated entries
+ crl.setLastRevocationDate(lastRevocationDateNew);
+ crl.setCrlNumberDelta(encodedCrlNumber.toString());
+ crl.setNextUpdateDelta(deltaCrl.getNextUpdate());
+ crlRepository.save(crl);
+ }
+ }
+
+
+ private CrlEntry createCrlEntry(X509CRLEntry x509CRLEntry, Crl crl) {
+ CrlEntry crlEntry = new CrlEntry();
+ crlEntry.setCrl(crl);
+ crlEntry.getId().setSerialNumber(x509CRLEntry.getSerialNumber().toString(16));
+ crlEntry.getId().setCrlUuid(crl.getUuid());
+ crlEntry.setRevocationDate(x509CRLEntry.getRevocationDate());
+ crlEntry.setRevocationReason(x509CRLEntry.getRevocationReason() == null ? CertificateRevocationReason.UNSPECIFIED : CertificateRevocationReason.fromCrlReason(x509CRLEntry.getRevocationReason()));
+ crlEntry.setCrl(crl);
+ crlEntryRepository.save(crlEntry);
+ return crlEntry;
+ }
+
+}
diff --git a/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java
index 8999ea4d2..27754d94d 100644
--- a/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/CryptographicKeyServiceImpl.java
@@ -846,7 +846,8 @@ private void createKeyAndItems(UUID connectorUuid, TokenInstanceReference tokenI
item.getKeyData(),
cryptographicKey,
connectorUuid,
- true
+ true,
+ false
)
);
}
@@ -881,7 +882,7 @@ private CryptographicKey createKeyEntity(KeyRequestDto request, TokenProfile tok
return key;
}
- private CryptographicKeyItem createKeyContent(String referenceUuid, String referenceName, KeyData keyData, CryptographicKey cryptographicKey, UUID connectorUuid, boolean isDiscovered) {
+ private CryptographicKeyItem createKeyContent(String referenceUuid, String referenceName, KeyData keyData, CryptographicKey cryptographicKey, UUID connectorUuid, boolean isDiscovered, boolean enabled) {
logger.info("Creating the Key Content for {}", cryptographicKey);
CryptographicKeyItem content = new CryptographicKeyItem();
content.setName(referenceName);
@@ -893,7 +894,7 @@ private CryptographicKeyItem createKeyContent(String referenceUuid, String refer
content.setLength(keyData.getLength());
content.setKeyReferenceUuid(UUID.fromString(referenceUuid));
content.setState(KeyState.ACTIVE);
- content.setEnabled(false);
+ content.setEnabled(enabled);
if (cryptographicKey.getTokenProfile() != null) {
content.setUsage(
cryptographicKey
@@ -1020,6 +1021,7 @@ private List mergeAndValidateAttributes(KeyRequestType type, Toke
}
private CryptographicKey createKeyTypeOfKeyPair(Connector connector, TokenProfile tokenProfile, KeyRequestDto request, CreateKeyRequestDto createKeyRequestDto, List attributes) throws ConnectorException {
+ boolean enabled = Boolean.TRUE.equals(request.getEnabled());
KeyPairDataResponseDto response = keyManagementApiClient.createKeyPair(
connector.mapToDto(),
tokenProfile.getTokenInstanceReference().getTokenInstanceUuid(),
@@ -1039,7 +1041,8 @@ private CryptographicKey createKeyTypeOfKeyPair(Connector connector, TokenProfil
response.getPrivateKeyData().getKeyData(),
key,
connector.getUuid(),
- false
+ false,
+ enabled
));
children.add(createKeyContent(
response.getPublicKeyData().getUuid(),
@@ -1047,7 +1050,8 @@ private CryptographicKey createKeyTypeOfKeyPair(Connector connector, TokenProfil
response.getPublicKeyData().getKeyData(),
key,
connector.getUuid(),
- false
+ false,
+ enabled
));
key.setItems(children);
try {
@@ -1082,7 +1086,9 @@ private CryptographicKey createKeyTypeOfSecret(Connector connector, TokenProfile
response.getKeyData(),
key,
connector.getUuid(),
- false
+ false,
+ Boolean.TRUE.equals(request.getEnabled()
+ )
)
)
);
diff --git a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java
index dff53bc1e..8694128f4 100644
--- a/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/DiscoveryServiceImpl.java
@@ -272,8 +272,9 @@ public void createDiscovery(DiscoveryHistory modal) {
modal.setStatus(DiscoveryStatus.WARNING);
modal.setMessage(
"Discovery exceeded maximum time of " + MAXIMUM_WAIT_TIME / (60 * 60) + " hours. There are no changes in number of certificates discovered. Please abort the discovery if the provider is stuck in IN_PROGRESS");
+ discoveryRepository.save(modal);
}
- discoveryRepository.save(modal);
+
oldCertificateCount = response.getTotalCertificatesDiscovered();
waitForCompletion = checkForCompletion(response);
}
@@ -285,6 +286,11 @@ public void createDiscovery(DiscoveryHistory modal) {
getRequest.setPageNumber(currentPage);
getRequest.setItemsPerPage(MAXIMUM_CERTIFICATES_PER_PAGE);
response = discoveryApiClient.getDiscoveryData(connector.mapToDto(), getRequest, response.getUuid());
+
+ if(response.getCertificateData().isEmpty()) {
+ modal.setMessage(String.format("Retrieved only %d certificates but provider discovered %d certificates in total.", currentTotal, response.getTotalCertificatesDiscovered()));
+ break;
+ }
if (response.getCertificateData().size() > MAXIMUM_CERTIFICATES_PER_PAGE) {
response.setStatus(DiscoveryStatus.FAILED);
updateDiscovery(modal, response);
diff --git a/src/main/java/com/czertainly/core/service/impl/NotificationServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/NotificationServiceImpl.java
index edb5a409d..4b399978b 100644
--- a/src/main/java/com/czertainly/core/service/impl/NotificationServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/NotificationServiceImpl.java
@@ -10,6 +10,8 @@
import com.czertainly.core.dao.entity.Notification;
import com.czertainly.core.dao.entity.NotificationRecipient;
import com.czertainly.core.dao.repository.NotificationRepository;
+import com.czertainly.core.security.authn.client.RoleManagementApiClient;
+import com.czertainly.core.security.authn.client.UserManagementApiClient;
import com.czertainly.core.security.authz.SecuredUUID;
import com.czertainly.core.service.NotificationService;
import com.czertainly.core.service.RoleManagementService;
@@ -39,10 +41,10 @@ public class NotificationServiceImpl implements NotificationService {
NotificationRepository notificationRepository;
@Autowired
- UserManagementService userService;
+ private UserManagementApiClient userManagementApiClient;
@Autowired
- RoleManagementService roleService;
+ private RoleManagementApiClient roleManagementApiClient;
@Override
public NotificationDto createNotificationForUser(String message, String detail, String userUuid, Resource target, String targetUuids) throws ValidationException {
@@ -77,12 +79,12 @@ public NotificationDto createNotificationForUsers(String message, String detail,
@Override
public NotificationDto createNotificationForGroup(String message, String detail, String groupUuid, Resource target, String targetUuids) throws ValidationException {
- return createNotificationForUsers(message, detail, userService.listUsers().stream().filter(u -> groupUuid.equals(u.getGroupUuid())).map(UserDto::getUuid).collect(Collectors.toList()), target, targetUuids);
+ return createNotificationForUsers(message, detail, userManagementApiClient.getUsers().getData().stream().filter(u -> groupUuid.equals(u.getGroupUuid())).map(UserDto::getUuid).toList(), target, targetUuids);
}
@Override
public NotificationDto createNotificationForRole(String message, String detail, String roleUuid, Resource target, String targetUuids) throws ValidationException {
- return createNotificationForUsers(message, detail, roleService.getRoleUsers(roleUuid).stream().map(UserDto::getUuid).collect(Collectors.toList()), target, targetUuids);
+ return createNotificationForUsers(message, detail, roleManagementApiClient.getRoleUsers(roleUuid).stream().map(UserDto::getUuid).toList(), target, targetUuids);
}
@Override
diff --git a/src/main/java/com/czertainly/core/service/impl/RaProfileServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/RaProfileServiceImpl.java
index d56c64070..4dbc42dcd 100644
--- a/src/main/java/com/czertainly/core/service/impl/RaProfileServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/RaProfileServiceImpl.java
@@ -10,9 +10,13 @@
import com.czertainly.api.model.common.NameAndUuidDto;
import com.czertainly.api.model.common.attribute.v2.BaseAttribute;
import com.czertainly.api.model.common.attribute.v2.DataAttribute;
+import com.czertainly.api.model.connector.authority.CaCertificatesRequestDto;
+import com.czertainly.api.model.connector.authority.CaCertificatesResponseDto;
+import com.czertainly.api.model.connector.v2.CertificateDataResponseDto;
import com.czertainly.api.model.core.audit.ObjectType;
import com.czertainly.api.model.core.audit.OperationType;
import com.czertainly.api.model.core.auth.Resource;
+import com.czertainly.api.model.core.certificate.CertificateDetailDto;
import com.czertainly.api.model.core.raprofile.RaProfileDto;
import com.czertainly.core.aop.AuditLogged;
import com.czertainly.core.dao.entity.*;
@@ -32,6 +36,8 @@
import com.czertainly.core.service.model.SecuredList;
import com.czertainly.core.service.v2.ExtendedAttributeService;
import com.czertainly.core.util.AttributeDefinitionUtils;
+import com.czertainly.core.util.CertificateUtil;
+import com.czertainly.core.util.X509ObjectToString;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
@@ -44,6 +50,9 @@
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -69,6 +78,7 @@ public class RaProfileServiceImpl implements RaProfileService {
private ScepProfileRepository scepProfileRepository;
private ApprovalProfileRelationRepository approvalProfileRelationRepository;
private ApprovalProfileRepository approvalProfileRepository;
+ private CertificateContentRepository certificateContentRepository;
@Override
@@ -105,13 +115,17 @@ public RaProfileDto addRaProfile(SecuredParentUUID authorityInstanceUuid, AddRaP
List attributes = mergeAndValidateAttributes(authorityInstanceRef, dto.getAttributes());
RaProfile raProfile = createRaProfile(dto, attributes, authorityInstanceRef);
+
raProfileRepository.save(raProfile);
+ setAuthorityCertificates(authorityInstanceRef, raProfile);
+
attributeService.createAttributeContent(raProfile.getUuid(), dto.getCustomAttributes(), Resource.RA_PROFILE);
RaProfileDto raProfileDto = raProfile.mapToDto();
raProfileDto.setCustomAttributes(attributeService.getCustomAttributesWithValues(raProfile.getUuid(), Resource.RA_PROFILE));
+
return raProfileDto;
}
@@ -497,6 +511,50 @@ public void disassociateApprovalProfile(String authorityInstanceUuid, String raP
approvalProfileRelationRepository.deleteAll(approvalProfileRelations);
}
+ @Override
+ @ExternalAuthorization(resource = Resource.RA_PROFILE, action = ResourceAction.DETAIL, parentResource = Resource.AUTHORITY, parentAction = ResourceAction.DETAIL)
+ public List getAuthorityCertificateChain(SecuredParentUUID authorityUuid, SecuredUUID raProfileUuid) throws ConnectorException {
+ RaProfile raProfile = getRaProfileEntity(raProfileUuid);
+ List requestAttributeDtos = AttributeDefinitionUtils.deserializeRequestAttributes(raProfile.getAttributes());
+ AuthorityInstanceReference authorityInstanceReference = authorityInstanceReferenceRepository.findByUuid(authorityUuid)
+ .orElseThrow(() -> new NotFoundException(AuthorityInstanceReference.class, authorityUuid));
+ CaCertificatesResponseDto caCertificatesResponseDto = authorityInstanceApiClient.getCaCertificates(authorityInstanceReference.getConnector().mapToDto(), authorityInstanceReference.getAuthorityInstanceUuid(), new CaCertificatesRequestDto(requestAttributeDtos));
+ List certificateDataResponseDtos = caCertificatesResponseDto.getCertificates();
+ List certificateDetailDtos = new ArrayList<>();
+ for (CertificateDataResponseDto certificateDataResponseDto : certificateDataResponseDtos) {
+ X509Certificate certificate;
+ String fingerprint;
+ try {
+ certificate = CertificateUtil.parseCertificate(certificateDataResponseDto.getCertificateData());
+ fingerprint = CertificateUtil.getThumbprint(certificate);
+ } catch (java.security.cert.CertificateException | NoSuchAlgorithmException e) {
+ logger.warn("Cannot process certificate from CA certificate chain returned from authority of RA profile {}", raProfile.getName());
+ break;
+ }
+
+ Optional existingCertificate = certificateRepository.findByFingerprint(fingerprint);
+ if (existingCertificate.isPresent()) {
+ certificateDetailDtos.add(existingCertificate.get().mapToDto());
+ } else {
+ Certificate modal = new Certificate();
+ CertificateUtil.prepareIssuedCertificate(modal, certificate);
+ CertificateContent certificateContent = certificateContentRepository.findByFingerprint(fingerprint);
+ if (certificateContent == null) {
+ certificateContent = new CertificateContent();
+ certificateContent.setContent(CertificateUtil.normalizeCertificateContent(X509ObjectToString.toPem(certificate)));
+ certificateContent.setFingerprint(fingerprint);
+ certificateContentRepository.save(certificateContent);
+ }
+ modal.setFingerprint(fingerprint);
+ modal.setCertificateContent(certificateContent);
+ modal.setCertificateContentId(certificateContent.getId());
+ certificateRepository.save(modal);
+ certificateDetailDtos.add(modal.mapToDto());
+ }
+ }
+ return certificateDetailDtos;
+ }
+
private List mergeAndValidateAttributes(AuthorityInstanceReference authorityInstanceRef, List attributes) throws ConnectorException {
List definitions = authorityInstanceApiClient.listRAProfileAttributes(
authorityInstanceRef.getConnector().mapToDto(),
@@ -544,6 +602,8 @@ private RaProfile updateRaProfile(RaProfile entity, AuthorityInstanceReference a
entity.setEnabled(dto.isEnabled() != null && dto.isEnabled());
}
entity.setAuthorityInstanceName(authorityInstanceRef.getName());
+
+ setAuthorityCertificates(authorityInstanceRef, entity);
return entity;
}
@@ -572,6 +632,18 @@ private List getComplianceProfilesForRaProfile(S
.map(ComplianceProfile::raProfileMapToDto).toList();
}
+ private void setAuthorityCertificates(AuthorityInstanceReference authorityInstanceRef, RaProfile raProfile) {
+ try {
+ List certificateChain = getAuthorityCertificateChain(SecuredParentUUID.fromUUID(authorityInstanceRef.getUuid()), SecuredUUID.fromUUID(raProfile.getUuid()));
+ raProfile.setAuthorityCertificateUuid(certificateChain.isEmpty() ? null : UUID.fromString(certificateChain.get(0).getUuid()));
+ } catch (NotFoundException ignored) {
+ // exception ignored since get CA certs from connector is optional
+ logger.debug("CA certificate chain not implemented for connector {}: {}",
+ authorityInstanceRef.getConnector().getName(), authorityInstanceRef.getConnector().getUuid());
+ } catch (Exception e) {
+ logger.warn("CA certificate chain from RA profile authority could not be retrieved: {}", e.getMessage());
+ }
+ }
// SETTERs
@@ -595,6 +667,7 @@ public void setCertificateRepository(CertificateRepository certificateRepository
this.certificateRepository = certificateRepository;
}
+
@Autowired
public void setAcmeProfileRepository(AcmeProfileRepository acmeProfileRepository) {
this.acmeProfileRepository = acmeProfileRepository;
@@ -644,4 +717,10 @@ public void setApprovalProfileRelationRepository(ApprovalProfileRelationReposito
public void setApprovalProfileRepository(ApprovalProfileRepository approvalProfileRepository) {
this.approvalProfileRepository = approvalProfileRepository;
}
+
+ @Autowired
+ public void setCertificateContentRepository(CertificateContentRepository certificateContentRepository) {
+ this.certificateContentRepository = certificateContentRepository;
+ }
+
}
diff --git a/src/main/java/com/czertainly/core/service/impl/RoleManagementServiceImpl.java b/src/main/java/com/czertainly/core/service/impl/RoleManagementServiceImpl.java
index 987917290..8108b391a 100644
--- a/src/main/java/com/czertainly/core/service/impl/RoleManagementServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/impl/RoleManagementServiceImpl.java
@@ -18,6 +18,7 @@
import org.springframework.stereotype.Service;
import jakarta.transaction.Transactional;
+
import java.util.List;
import java.util.UUID;
@@ -134,11 +135,13 @@ public void removeResourcePermissionObjects(String roleUuid, String resourceUuid
}
@Override
+ @ExternalAuthorization(resource = Resource.ROLE, action = ResourceAction.DETAIL)
public List getRoleUsers(String roleUuid) {
return roleManagementApiClient.getRoleUsers(roleUuid);
}
@Override
+ @ExternalAuthorization(resource = Resource.ROLE, action = ResourceAction.UPDATE)
public RoleDetailDto updateUsers(String roleUuid, List userUuids) {
return roleManagementApiClient.updateUsers(roleUuid, userUuids);
}
diff --git a/src/main/java/com/czertainly/core/service/v2/impl/ClientOperationServiceImpl.java b/src/main/java/com/czertainly/core/service/v2/impl/ClientOperationServiceImpl.java
index 6af9d4d3b..f3b03c342 100644
--- a/src/main/java/com/czertainly/core/service/v2/impl/ClientOperationServiceImpl.java
+++ b/src/main/java/com/czertainly/core/service/v2/impl/ClientOperationServiceImpl.java
@@ -681,6 +681,7 @@ public void revokeCertificateAction(final UUID certificateUuid, ClientCertificat
caRequest);
certificate.setState(CertificateState.REVOKED);
+ certificate.setRevokeAttributes(AttributeDefinitionUtils.serializeRequestAttributes(request.getAttributes()));
certificateRepository.save(certificate);
certificateEventHistoryService.addEventHistory(certificate.getUuid(), CertificateEvent.REVOKE, CertificateEventStatus.SUCCESS, "Certificate revoked. Reason: " + caRequest.getReason().getLabel(), "");
} catch (Exception e) {
@@ -692,21 +693,13 @@ public void revokeCertificateAction(final UUID certificateUuid, ClientCertificat
throw new CertificateOperationException("Failed to revoke certificate: " + e.getMessage());
}
- try {
- CertificateValidationStatus oldStatus = certificate.getValidationStatus();
- certificate.setValidationStatus(CertificateValidationStatus.REVOKED);
- certificate.setRevokeAttributes(AttributeDefinitionUtils.serialize(extendedAttributeService.mergeAndValidateIssueAttributes(raProfile, request.getAttributes())));
- logger.debug("Certificate revoked. Proceeding to check and destroy key");
-
- if (certificate.getKey() != null && request.isDestroyKey()) {
+ if (certificate.getKey() != null && request.isDestroyKey()) {
+ try {
+ logger.debug("Certificate revoked. Proceeding to check and destroy key");
keyService.destroyKey(List.of(certificate.getKeyUuid().toString()));
+ } catch (Exception e) {
+ logger.warn("Failed to destroy certificate key: {}", e.getMessage());
}
- certificateRepository.save(certificate);
-
- eventProducer.produceCertificateStatusChangeEventMessage(certificate.getUuid(), CertificateEvent.UPDATE_VALIDATION_STATUS, CertificateEventStatus.SUCCESS, oldStatus, CertificateValidationStatus.REVOKED);
- notificationProducer.produceNotificationCertificateStatusChanged(oldStatus, CertificateValidationStatus.REVOKED, certificate.mapToListDto());
- } catch (Exception e) {
- logger.warn(e.getMessage());
}
// notify
diff --git a/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java b/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java
index f693d3beb..84eea96b8 100644
--- a/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java
+++ b/src/main/java/com/czertainly/core/tasks/DiscoveryCertificateTask.java
@@ -36,7 +36,7 @@ public class DiscoveryCertificateTask extends SchedulerJobProcessor {
private ObjectMapper mapper = new ObjectMapper();
- private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
+ private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss.FFF");
@Autowired
public void setDiscoveryService(DiscoveryService discoveryService) {
diff --git a/src/main/java/com/czertainly/core/util/CertificateUtil.java b/src/main/java/com/czertainly/core/util/CertificateUtil.java
index cf96b7917..11e1ee9cb 100644
--- a/src/main/java/com/czertainly/core/util/CertificateUtil.java
+++ b/src/main/java/com/czertainly/core/util/CertificateUtil.java
@@ -355,8 +355,10 @@ public static void prepareIssuedCertificate(Certificate modal, X509Certificate c
}
modal.setKeyUsage(
MetaDefinitions.serializeArrayString(CertificateUtil.keyUsageExtractor(certificate.getKeyUsage())));
- modal.setBasicConstraints(CertificateUtil.getBasicConstraint(certificate.getBasicConstraints()));
+ String basicConstraints = CertificateUtil.getBasicConstraint(certificate.getBasicConstraints());
+ modal.setBasicConstraints(basicConstraints);
+ if (basicConstraints.equals("Subject Type=CA")) modal.setTrustedCa(false);
}
diff --git a/src/main/java/com/czertainly/core/util/CrlUtil.java b/src/main/java/com/czertainly/core/util/CrlUtil.java
index d8ea6ce59..8fb766e85 100644
--- a/src/main/java/com/czertainly/core/util/CrlUtil.java
+++ b/src/main/java/com/czertainly/core/util/CrlUtil.java
@@ -4,45 +4,37 @@
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.Extension;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import java.security.GeneralSecurityException;
-import java.security.cert.*;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.List;
public class CrlUtil {
private static final Logger logger = LoggerFactory.getLogger(CrlUtil.class);
//CRL Timeout setting when initiating URL Connection. If the connection takes more than 30 seconds, it is determined as not reachable
- private static final Integer CRL_CONNECTION_TIMEOUT = 30; //seconds
+ private static final Integer CRL_CONNECTION_TIMEOUT = 1000; //milliseconds
private CrlUtil() {
}
- public static List getCDPFromCertificate(X509Certificate certificate) throws IOException {
- byte[] crlDistributionPointDerEncodedArray = certificate
- .getExtensionValue(Extension.cRLDistributionPoints.getId());
- if (crlDistributionPointDerEncodedArray == null) {
+ public static List getCDPFromCertificate(byte[] extensionToDownloadFrom) throws IOException {
+
+ if (extensionToDownloadFrom == null) {
return new ArrayList<>();
}
ASN1InputStream oAsnInStream = new ASN1InputStream(
- new ByteArrayInputStream(crlDistributionPointDerEncodedArray));
+ new ByteArrayInputStream(extensionToDownloadFrom));
ASN1Primitive derObjCrlDP = oAsnInStream.readObject();
DEROctetString dosCrlDP = (DEROctetString) derObjCrlDP;
@@ -62,9 +54,9 @@ public static List getCDPFromCertificate(X509Certificate certificate) th
if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
// Look for an URI
- for (int j = 0; j < genNames.length; j++) {
- if (genNames[j].getTagNo() == GeneralName.uniformResourceIdentifier) {
- String url = DERIA5String.getInstance(genNames[j].getName()).getString();
+ for (GeneralName genName : genNames) {
+ if (genName.getTagNo() == GeneralName.uniformResourceIdentifier) {
+ String url = DERIA5String.getInstance(genName.getName()).getString();
crlUrls.add(url);
}
}
@@ -73,27 +65,19 @@ public static List getCDPFromCertificate(X509Certificate certificate) th
return crlUrls;
}
- public static String checkCertificateRevocationList(X509Certificate certificate, String crlUrl) throws IOException, CertificateException, CRLException {
- X509CRL crl;
+ public static X509CRL getX509Crl(String crlUrl) throws IOException, CertificateException {
+ X509CRL X509Crl;
URL url = new URL(crlUrl);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(CRL_CONNECTION_TIMEOUT);
CertificateFactory cf = CertificateFactory.getInstance("X509");
try (DataInputStream inStream = new DataInputStream(connection.getInputStream())) {
- crl = (X509CRL) cf.generateCRL(inStream);
- } catch (FileNotFoundException e) {
+ X509Crl = (X509CRL) cf.generateCRL(inStream);
+ } catch (CRLException e) {
throw new CertificateException("File " + e.getMessage() + " not found");
}
- X509CRLEntry crlCertificate = crl.getRevokedCertificate(certificate.getSerialNumber());
- if (crlCertificate == null) {
- return null;
- } else {
- DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
- String strDate = dateFormat.format(crlCertificate.getRevocationDate());
- String reason = crlCertificate.getRevocationReason() != null ? crlCertificate.getRevocationReason().toString() : "Unspecified";
-
- return String.format("Reason: %s. Date: %s", reason, strDate);
- }
+ return X509Crl;
}
+
}
diff --git a/src/main/java/com/czertainly/core/util/converter/CertificateFormatEncodingConverter.java b/src/main/java/com/czertainly/core/util/converter/CertificateFormatEncodingConverter.java
new file mode 100644
index 000000000..58cda1a09
--- /dev/null
+++ b/src/main/java/com/czertainly/core/util/converter/CertificateFormatEncodingConverter.java
@@ -0,0 +1,11 @@
+package com.czertainly.core.util.converter;
+
+import com.czertainly.api.model.core.certificate.CertificateFormatEncoding;
+
+import java.beans.PropertyEditorSupport;
+
+public class CertificateFormatEncodingConverter extends PropertyEditorSupport {
+ public void setAsText(final String text) throws IllegalArgumentException {
+ setValue(CertificateFormatEncoding.fromCode(text));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java b/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java
index 3df04b11a..c90a0de79 100644
--- a/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java
+++ b/src/main/java/com/czertainly/core/validation/certificate/X509CertificateValidator.java
@@ -1,21 +1,31 @@
package com.czertainly.core.validation.certificate;
+import com.czertainly.api.exception.ValidationException;
import com.czertainly.api.model.core.certificate.CertificateState;
import com.czertainly.api.model.core.certificate.CertificateValidationCheck;
import com.czertainly.api.model.core.certificate.CertificateValidationCheckDto;
import com.czertainly.api.model.core.certificate.CertificateValidationStatus;
import com.czertainly.core.dao.entity.Certificate;
+import com.czertainly.core.dao.entity.Crl;
+import com.czertainly.core.dao.entity.CrlEntry;
import com.czertainly.core.dao.repository.CertificateRepository;
+import com.czertainly.core.dao.repository.CrlEntryRepository;
+import com.czertainly.core.dao.repository.CrlRepository;
+import com.czertainly.core.service.CrlService;
import com.czertainly.core.util.CertificateUtil;
import com.czertainly.core.util.CrlUtil;
+import com.czertainly.core.util.CzertainlyX500NameStyle;
import com.czertainly.core.util.OcspUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
+import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Instant;
@@ -28,14 +38,22 @@ public class X509CertificateValidator implements ICertificateValidator {
private static final Logger logger = LoggerFactory.getLogger(X509CertificateValidator.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final int DAYS_TO_EXPIRE = 30;
-
private CertificateRepository certificateRepository;
+
+ private CrlService crlService;
+
@Autowired
public void setCertificateRepository(CertificateRepository certificateRepository) {
this.certificateRepository = certificateRepository;
}
+ @Autowired
+ public void setCrlService(CrlService crlService) {
+ this.crlService = crlService;
+ }
+
+
@Override
public CertificateValidationStatus validateCertificate(Certificate certificate, boolean isCompleteChain) throws CertificateException {
logger.debug("Initiating the certificate validation: {}", certificate);
@@ -56,7 +74,7 @@ public CertificateValidationStatus validateCertificate(Certificate certificate,
x509Certificate = CertificateUtil.getX509Certificate(certificateChain.get(i).getCertificateContent().getContent());
boolean isEndCertificate = i == 0;
- validationOutput = validatePathCertificate(x509Certificate, x509IssuerCertificate, previousCertStatus, isCompleteChain, isEndCertificate);
+ validationOutput = validatePathCertificate(x509Certificate, x509IssuerCertificate, certificateChain.get(i).getTrustedCa(), previousCertStatus, isCompleteChain, isEndCertificate, certificateChain.get(i).getIssuerDnNormalized());
CertificateValidationStatus resultStatus = calculateResultStatus(validationOutput);
finalizeValidation(certificateChain.get(i), resultStatus, validationOutput);
@@ -68,7 +86,7 @@ public CertificateValidationStatus validateCertificate(Certificate certificate,
return previousCertStatus;
}
- private Map validatePathCertificate(X509Certificate certificate, X509Certificate issuerCertificate, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain, boolean isEndCertificate) {
+ private Map validatePathCertificate(X509Certificate certificate, X509Certificate issuerCertificate, Boolean trustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain, boolean isEndCertificate, String issuerDn) {
Map validationOutput = initializeValidationOutput();
// check certificate signature
@@ -82,11 +100,11 @@ private Map validateP
// check if certificate is not revoked - OCSP & CRL
// section (a)(3) in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.3
validationOutput.put(CertificateValidationCheck.OCSP_VERIFICATION, checkOcspRevocationStatus(certificate, issuerCertificate));
- validationOutput.put(CertificateValidationCheck.CRL_VERIFICATION, checkCrlRevocationStatus(certificate, issuerCertificate));
+ validationOutput.put(CertificateValidationCheck.CRL_VERIFICATION, checkCrlRevocationStatus(certificate, issuerCertificate, isCompleteChain));
// check certificate issuer DN and if certificate chain is valid
// section (a)(4) in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.3
- validationOutput.put(CertificateValidationCheck.CERTIFICATE_CHAIN, checkCertificateChain(certificate, issuerCertificate, issuerCertificateStatus, isCompleteChain));
+ validationOutput.put(CertificateValidationCheck.CERTIFICATE_CHAIN, checkCertificateChain(certificate, issuerCertificate, trustedCa, issuerCertificateStatus, isCompleteChain));
// (k) and (l) section in https://datatracker.ietf.org/doc/html/rfc5280#section-6.1.4
validationOutput.put(CertificateValidationCheck.BASIC_CONSTRAINTS, checkBasicConstraints(certificate, issuerCertificate, isEndCertificate));
@@ -97,11 +115,15 @@ private Map validateP
return validationOutput;
}
- private CertificateValidationCheckDto checkCertificateChain(X509Certificate certificate, X509Certificate issuerCertificate, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain) {
+ private CertificateValidationCheckDto checkCertificateChain(X509Certificate certificate, X509Certificate issuerCertificate, Boolean isTrustedCa, CertificateValidationStatus issuerCertificateStatus, boolean isCompleteChain) {
if (issuerCertificate == null) {
// should be trust anchor (Root CA certificate)
if (isCompleteChain) {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.VALID, "Certificate chain is complete. Certificate is Root CA certificate (trusted anchor).");
+ if (Boolean.TRUE.equals(isTrustedCa)) {
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.VALID, "Certificate chain is complete. Certificate is trusted root CA certificate.");
+ } else {
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.INVALID, "Certificate chain is complete. Certificate is root CA certificate but not marked as trusted.");
+ }
} else {
return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.INVALID, "Incomplete certificate chain. Issuer certificate is not available in the inventory or in the AIA extension.");
}
@@ -117,13 +139,15 @@ private CertificateValidationCheckDto checkCertificateChain(X509Certificate cert
}
if (isCompleteChain) {
- if (issuerNameEqualityMessage.isEmpty() && issuerStatusMessage.isEmpty()) {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.VALID, "Certificate chain is complete.");
- } else {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.INVALID, "Certificate chain is complete. " + issuerNameEqualityMessage + issuerStatusMessage);
+ String trustedCaMessage = "";
+ if (isTrustedCa != null) {
+ trustedCaMessage = Boolean.TRUE.equals(isTrustedCa) ? " Certificate is trusted intermediate CA." : " Certificate is intermediate CA certificate but not marked as trusted.";
}
+
+ CertificateValidationStatus chainValidationStatus = issuerNameEqualityMessage.isEmpty() && issuerStatusMessage.isEmpty() && (trustedCaMessage.isEmpty() || Boolean.TRUE.equals(isTrustedCa)) ? CertificateValidationStatus.VALID : CertificateValidationStatus.INVALID;
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, chainValidationStatus, String.format("Certificate chain is complete.%s%s%s", trustedCaMessage, issuerNameEqualityMessage, issuerStatusMessage));
} else {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.INVALID, "Incomplete certificate chain. Missing certificate in validation path." + issuerNameEqualityMessage + issuerStatusMessage);
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CERTIFICATE_CHAIN, CertificateValidationStatus.INVALID, String.format("Incomplete certificate chain. Missing certificate in validation path.%s%s", issuerNameEqualityMessage, issuerStatusMessage));
}
}
}
@@ -218,58 +242,49 @@ private CertificateValidationCheckDto checkOcspRevocationStatus(X509Certificate
return new CertificateValidationCheckDto(CertificateValidationCheck.OCSP_VERIFICATION, ocspOutputStatus, ocspMessage.toString());
}
- private CertificateValidationCheckDto checkCrlRevocationStatus(X509Certificate certificate, X509Certificate issuerCertificate) {
+ private CertificateValidationCheckDto checkCrlRevocationStatus(X509Certificate certificate, X509Certificate issuerCertificate, boolean isCompleteChain) {
if (issuerCertificate == null) {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.NOT_CHECKED, "Issuer certificate is not available.");
+ if (!isCompleteChain)
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.NOT_CHECKED, "Issuer certificate is not available.");
+ issuerCertificate = certificate;
}
- List crlUrls;
+ if (certificate.getExtensionValue(Extension.cRLDistributionPoints.getId()) == null) {
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.NOT_CHECKED, "The cRLDistributionPoints extension is not set.");
+ }
+ Crl crl;
try {
- crlUrls = CrlUtil.getCDPFromCertificate(certificate);
+ crl = crlService.getCurrentCrl(certificate, issuerCertificate);
} catch (IOException e) {
return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.FAILED, "Failed to retrieve CRL URL from certificate: " + e.getMessage());
+ } catch (ValidationException e) {
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.FAILED, "Failed to process CRL: " + e.getMessage());
}
- if (crlUrls.isEmpty()) {
- return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.NOT_CHECKED, "No CRL URL in certificate");
+ if (crl == null) {
+ return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, CertificateValidationStatus.NOT_CHECKED, "No available working CRL URL found in cRLDistributionPoints extension.");
}
- String crlOutput = "";
StringBuilder crlMessage = new StringBuilder();
- CertificateValidationStatus crlOutputStatus = CertificateValidationStatus.NOT_CHECKED;
- for (String crlUrl : crlUrls) {
- try {
- crlOutput = CrlUtil.checkCertificateRevocationList(certificate, crlUrl);
- if (crlOutput == null) {
- if (crlOutputStatus.equals(CertificateValidationStatus.NOT_CHECKED)) {
- crlOutputStatus = CertificateValidationStatus.VALID;
- }
- crlMessage.append("CRL verification successful from URL ");
- crlMessage.append(crlUrl);
- crlMessage.append(". ");
- } else {
- crlOutputStatus = CertificateValidationStatus.REVOKED;
- crlMessage.append("Certificate was revoked according to information from CRL URL ");
- crlMessage.append(crlUrl);
- crlMessage.append(". ");
- crlMessage.append(crlOutput);
- crlMessage.append(". ");
- break;
- }
- } catch (Exception e) {
- logger.debug("Not able to check CRL: {}", e.getMessage());
- crlOutputStatus = CertificateValidationStatus.FAILED;
- crlMessage.append("Error while checking CRL URL ");
- crlMessage.append(crlUrl);
- crlMessage.append(". Error: ");
- crlMessage.append(e.getMessage());
- crlMessage.append(". ");
- }
- }
+ CertificateValidationStatus crlOutputStatus;
+
+ CrlEntry crlEntry = crlService.findCrlEntryForCertificate(certificate.getSerialNumber().toString(16), crl.getUuid());
+ if (crlEntry == null) {
+ crlOutputStatus = CertificateValidationStatus.VALID;
+ crlMessage.append("CRL verification successful from URL");
+ crlMessage.append(". ");
+ } else {
+ crlOutputStatus = CertificateValidationStatus.REVOKED;
+ crlMessage.append("Certificate was revoked according to information from CRL URL");
+ crlMessage.append(". Revocation reason: ");
+ crlMessage.append(crlEntry.getRevocationReason().getLabel());
+ crlMessage.append(". ");
+ }
return new CertificateValidationCheckDto(CertificateValidationCheck.CRL_VERIFICATION, crlOutputStatus, crlMessage.toString());
}
+
private CertificateValidationCheckDto checkBasicConstraints(X509Certificate certificate, X509Certificate issuerCertificate, boolean isEndCertificate) {
int pathLenConstraint = certificate.getBasicConstraints();
boolean isCa = pathLenConstraint >= 0;
@@ -383,4 +398,5 @@ private Map initializ
validationOutput.put(CertificateValidationCheck.KEY_USAGE, new CertificateValidationCheckDto(CertificateValidationCheck.KEY_USAGE, CertificateValidationStatus.NOT_CHECKED, null));
return validationOutput;
}
+
}
diff --git a/src/main/resources/db/migration/V202311131230__trusted_ca_mark.sql b/src/main/resources/db/migration/V202311131230__trusted_ca_mark.sql
new file mode 100644
index 000000000..52d97864c
--- /dev/null
+++ b/src/main/resources/db/migration/V202311131230__trusted_ca_mark.sql
@@ -0,0 +1,2 @@
+ALTER TABLE certificate ADD COLUMN trusted_ca BOOLEAN NULL;
+UPDATE certificate SET trusted_ca = false WHERE basic_constraints LIKE '%Subject Type=CA%';
diff --git a/src/main/resources/db/migration/V202312141140__authority_certifcat_uuid.sql b/src/main/resources/db/migration/V202312141140__authority_certifcat_uuid.sql
new file mode 100644
index 000000000..19587ced6
--- /dev/null
+++ b/src/main/resources/db/migration/V202312141140__authority_certifcat_uuid.sql
@@ -0,0 +1 @@
+ALTER TABLE ra_profile ADD COLUMN authority_certificate_uuid UUID NULL;
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V202401171122__ca_crl_management.sql b/src/main/resources/db/migration/V202401171122__ca_crl_management.sql
new file mode 100644
index 000000000..b3450df99
--- /dev/null
+++ b/src/main/resources/db/migration/V202401171122__ca_crl_management.sql
@@ -0,0 +1,24 @@
+CREATE TABLE crl (
+ uuid UUID NOT NULL,
+ ca_certificate_uuid UUID,
+ issuer_dn VARCHAR NOT NULL,
+ serial_number VARCHAR NOT NULL,
+ crl_issuer_dn VARCHAR NOT NULL,
+ crl_number VARCHAR NOT NULL,
+ next_update TIMESTAMP NOT NULL,
+ crl_number_delta VARCHAR,
+ next_update_delta TIMESTAMP,
+ last_revocation_date TIMESTAMP,
+ PRIMARY KEY (uuid),
+ FOREIGN KEY (ca_certificate_uuid) REFERENCES certificate(uuid)
+);
+
+CREATE TABLE crl_entry (
+ crl_uuid UUID,
+ serial_number VARCHAR,
+ revocation_date TIMESTAMP NOT NULL,
+ revocation_reason VARCHAR NOT NULL,
+ PRIMARY KEY (crl_uuid, serial_number),
+ FOREIGN KEY (crl_uuid) REFERENCES crl(uuid) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
diff --git a/src/test/java/com/czertainly/core/service/CertificateServiceTest.java b/src/test/java/com/czertainly/core/service/CertificateServiceTest.java
index 4f72f5d83..cc7893c74 100644
--- a/src/test/java/com/czertainly/core/service/CertificateServiceTest.java
+++ b/src/test/java/com/czertainly/core/service/CertificateServiceTest.java
@@ -9,9 +9,7 @@
import com.czertainly.api.model.common.attribute.v2.content.StringAttributeContent;
import com.czertainly.api.model.common.attribute.v2.properties.MetadataAttributeProperties;
import com.czertainly.api.model.core.auth.Resource;
-import com.czertainly.api.model.core.certificate.CertificateDetailDto;
-import com.czertainly.api.model.core.certificate.CertificateState;
-import com.czertainly.api.model.core.certificate.CertificateValidationStatus;
+import com.czertainly.api.model.core.certificate.*;
import com.czertainly.api.model.core.connector.ConnectorStatus;
import com.czertainly.api.model.core.connector.FunctionGroupCode;
import com.czertainly.api.model.core.search.SearchFieldDataByGroupDto;
@@ -353,4 +351,21 @@ public void testBulkRemove() throws NotFoundException {
Assertions.assertThrows(NotFoundException.class, () -> certificateService.getCertificate(certificate.getSecuredUuid()));
}
+
+ @Test
+ public void testDownloadCertificate() throws NotFoundException, CertificateException, IOException {
+ CertificateContent certificateContentDownload = new CertificateContent();
+ certificateContentDownload.setContent("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");
+ certificateContentDownload = certificateContentRepository.save(certificateContentDownload);
+ certificate.setCertificateContent(certificateContentDownload);
+ testDownloadInternal(CertificateFormat.RAW, CertificateFormatEncoding.PEM);
+ testDownloadInternal(CertificateFormat.RAW, CertificateFormatEncoding.DER);
+ testDownloadInternal(CertificateFormat.PKCS7, CertificateFormatEncoding.PEM);
+ testDownloadInternal(CertificateFormat.PKCS7, CertificateFormatEncoding.DER);
+ }
+
+ private void testDownloadInternal(CertificateFormat format, CertificateFormatEncoding encoding) throws NotFoundException, CertificateException, IOException {
+ CertificateDownloadResponseDto certificateDownloadResponseDto = certificateService.downloadCertificate(certificate.getUuid().toString(), format, encoding);
+ Assertions.assertDoesNotThrow(() -> (certificateService.createCertificate(certificateDownloadResponseDto.getContent(), CertificateType.X509)));
+ }
}
diff --git a/src/test/java/com/czertainly/core/service/CertificateValidationTest.java b/src/test/java/com/czertainly/core/service/CertificateValidationTest.java
index 425fe9924..f5e5774c8 100644
--- a/src/test/java/com/czertainly/core/service/CertificateValidationTest.java
+++ b/src/test/java/com/czertainly/core/service/CertificateValidationTest.java
@@ -1,17 +1,34 @@
package com.czertainly.core.service;
import com.czertainly.api.exception.NotFoundException;
+import com.czertainly.api.exception.ValidationException;
import com.czertainly.api.model.core.certificate.*;
import com.czertainly.core.dao.entity.Certificate;
import com.czertainly.core.dao.entity.CertificateContent;
+import com.czertainly.core.dao.entity.Crl;
import com.czertainly.core.dao.repository.CertificateContentRepository;
import com.czertainly.core.dao.repository.CertificateRepository;
+import com.czertainly.core.dao.repository.CrlEntryRepository;
+import com.czertainly.core.dao.repository.CrlRepository;
import com.czertainly.core.util.BaseSpringBootTest;
+import com.czertainly.core.util.CertificateUtil;
import com.czertainly.core.util.MetaDefinitions;
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
import org.apache.commons.lang3.StringUtils;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.*;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
@@ -24,17 +41,22 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.cert.*;
import java.util.*;
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+
@SpringBootTest
@Transactional
@Rollback
@@ -43,11 +65,20 @@ public class CertificateValidationTest extends BaseSpringBootTest {
@Autowired
private CertificateService certificateService;
+ @Autowired
+ private CrlService crlService;
+
@Autowired
private CertificateRepository certificateRepository;
@Autowired
private CertificateContentRepository certificateContentRepository;
+ @Autowired
+ private CrlRepository crlRepository;
+
+ @Autowired
+ private CrlEntryRepository crlEntryRepository;
+
private Certificate certificate;
private CertificateContent certificateContent;
@@ -61,8 +92,15 @@ public class CertificateValidationTest extends BaseSpringBootTest {
private static final Logger logger = LoggerFactory.getLogger(CertificateValidationTest.class);
+ private WireMockServer mockServer;
+
+
@BeforeEach
public void setUp() throws GeneralSecurityException, IOException, com.czertainly.api.exception.CertificateException {
+ mockServer = new WireMockServer(0);
+ mockServer.start();
+
+ WireMock.configureFor("localhost", mockServer.port());
InputStream keyStoreStream = CertificateServiceTest.class.getClassLoader().getResourceAsStream("client1.p12");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreStream, "123456".toCharArray());
@@ -89,6 +127,8 @@ public void setUp() throws GeneralSecurityException, IOException, com.czertainly
caCertificate = certificateService.createCertificate(caCertificateContent, CertificateType.X509);
+ certificateRepository.save(caCertificate);
+
String chainCertificateContent = "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";
chainIncompleteCertificate = certificateService.createCertificate(chainCertificateContent, CertificateType.X509);
@@ -138,21 +178,123 @@ void testGetCertificateChain() throws NotFoundException {
@Test
void testDownloadCertificateChain() throws NotFoundException, CertificateException, CMSException, IOException {
- CertificateChainDownloadResponseDto certificateChainIncompletePEMDownloadResponseDto = certificateService.downloadCertificateChain(chainIncompleteCertificate.getSecuredUuid(), CertificateFormat.PEM, true);
+ CertificateChainDownloadResponseDto certificateChainIncompletePEMDownloadResponseDto = certificateService.downloadCertificateChain(chainIncompleteCertificate.getSecuredUuid(), CertificateFormat.RAW, true, CertificateFormatEncoding.PEM);
Assertions.assertEquals(2, getNumberOfCertificatesInPem(certificateChainIncompletePEMDownloadResponseDto.getContent()));
Assertions.assertTrue(certificateChainIncompletePEMDownloadResponseDto.isCompleteChain());
- CertificateChainDownloadResponseDto certificateChainCompletePEMResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.PEM, true);
+ CertificateChainDownloadResponseDto certificateChainCompletePEMResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.RAW, true, CertificateFormatEncoding.PEM);
Assertions.assertTrue(certificateChainCompletePEMResponseDto.isCompleteChain());
Assertions.assertEquals(2, getNumberOfCertificatesInPem(certificateChainCompletePEMResponseDto.getContent()));
- CertificateChainDownloadResponseDto certificateChainCompletePKCS7ResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.PKCS7, true);
+ CertificateChainDownloadResponseDto certificateChainCompletePKCS7ResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.PKCS7, true, CertificateFormatEncoding.PEM);
Assertions.assertTrue(certificateChainCompletePKCS7ResponseDto.isCompleteChain());
Assertions.assertEquals(2, getNumberOfCertificatesInPkcs7(certificateChainCompletePKCS7ResponseDto.getContent()));
- CertificateChainDownloadResponseDto certificateChainResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.PKCS7, false);
+ CertificateChainDownloadResponseDto certificateChainResponseDto = certificateService.downloadCertificateChain(chainCompleteCertificate.getSecuredUuid(), CertificateFormat.PKCS7, false, CertificateFormatEncoding.PEM);
Assertions.assertTrue(certificateChainResponseDto.isCompleteChain());
Assertions.assertEquals(1, getNumberOfCertificatesInPkcs7(certificateChainResponseDto.getContent()));
}
+ @Test
+ void testCrlProcessing() throws GeneralSecurityException, OperatorCreationException, IOException, NotFoundException {
+
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
+ keyPairGen.initialize(2048);
+ KeyPair pair = keyPairGen.generateKeyPair();
+
+ // prepare certs
+ X509Certificate x509CaCertificate = CertificateUtil.getX509Certificate(caCertificate.getCertificateContent().getContent());
+
+ List crlUrls = List.of(mockServer.baseUrl() + "/crl1.crl", mockServer.baseUrl() + "/crl2.crl");
+ X509Certificate certificateWithCrl = createSelfSignedCertificateWithCrl("testCrl", crlUrls, null);
+ Certificate certificateWithCrlEntity = certificateService.createCertificateEntity(certificateWithCrl);
+ certificateWithCrlEntity.setTrustedCa(true);
+ certificateRepository.save(certificateWithCrlEntity);
+
+ X509Certificate certificateWithDelta = createSelfSignedCertificateWithCrl("testCrlWithDelta", crlUrls, List.of(mockServer.baseUrl() + "/deltaCrl"));
+ Certificate certificateWithCrlDeltaEntity = certificateService.createCertificateEntity(certificateWithDelta);
+ certificateWithCrlDeltaEntity.setTrustedCa(true);
+ certificateRepository.save(certificateWithCrlDeltaEntity);
+
+ // Test CRL without the end certificate and without delta
+ X509CRL emptyX509Crl = createEmptyCRL(x509CaCertificate, pair.getPrivate());
+ byte[] emptyX509CrlBytes = emptyX509Crl.getEncoded();
+ stubCrlPoint("/crl1.crl", emptyX509CrlBytes);
+ var validationResult = certificateService.getCertificateValidationResult(certificateWithCrlEntity.getSecuredUuid());
+ Assertions.assertEquals(CertificateValidationStatus.VALID, validationResult.getValidationChecks().get(CertificateValidationCheck.CRL_VERIFICATION).getStatus());
+ Crl crl = crlService.getCurrentCrl(certificateWithCrl, certificateWithCrl);
+ Assertions.assertNull(crlService.findCrlEntryForCertificate(certificateWithCrl.getSerialNumber().toString(16), crl.getUuid()));
+
+ // Test CRL with revoked certificate and without delta and with one invalid CRL distribution point
+ crlRepository.delete(crl);
+ mockServer.removeStubMapping(mockServer.getStubMappings().get(0));
+ X509CRL X509CrlRevokedCert = addRevocationToCRL(pair.getPrivate(), "SHA256WithRSAEncryption", emptyX509Crl, certificateWithCrl);
+ stubCrlPoint("/crl2.crl", X509CrlRevokedCert.getEncoded());
+ validationResult = certificateService.getCertificateValidationResult(certificateWithCrlEntity.getSecuredUuid());
+ Assertions.assertEquals(CertificateValidationStatus.REVOKED, validationResult.getValidationChecks().get(CertificateValidationCheck.CRL_VERIFICATION).getStatus());
+ Crl crlWithRevoked = crlService.getCurrentCrl(certificateWithCrl, certificateWithCrl);
+ Assertions.assertNotNull(crlService.findCrlEntryForCertificate(certificateWithCrl.getSerialNumber().toString(16), crlWithRevoked.getUuid()));
+
+ // Test properly set deltaCrl
+ X509CRL deltaCrl = createEmptyDeltaCRL(x509CaCertificate, pair.getPrivate(), BigInteger.valueOf(Integer.parseInt(crlWithRevoked.getCrlNumber())), BigInteger.ONE);
+ stubCrlPoint("/deltaCrl", deltaCrl.getEncoded());
+ Crl crlWithDelta = crlService.getCurrentCrl(certificateWithDelta, certificateWithDelta);
+ Assertions.assertNotNull(crlWithDelta.getCrlNumberDelta());
+ Assertions.assertNotNull(crlWithDelta.getNextUpdateDelta());
+
+ // Test improperly set deltaCrl
+ crlRepository.delete(crlWithDelta);
+ X509CRL deltaCrl2 = createEmptyDeltaCRL(x509CaCertificate, pair.getPrivate(), BigInteger.TWO, BigInteger.ONE);
+ stubCrlPoint("/deltaCrl", deltaCrl2.getEncoded());
+ validationResult = certificateService.getCertificateValidationResult(certificateWithCrlDeltaEntity.getSecuredUuid());
+ Assertions.assertEquals(CertificateValidationStatus.FAILED, validationResult.getValidationChecks().get(CertificateValidationCheck.CRL_VERIFICATION).getStatus());
+ Assertions.assertThrows(ValidationException.class, () -> crlService.getCurrentCrl(certificateWithDelta, certificateWithDelta));
+
+ // Test deltaCrl with revoked cert
+ crlRepository.deleteAll();
+ X509CRL deltaCrl3 = createEmptyDeltaCRL(x509CaCertificate, pair.getPrivate(), BigInteger.valueOf(Integer.parseInt(crlWithRevoked.getCrlNumber())), BigInteger.TWO);
+ X509CRL deltaCrlWithRevoked = addRevocationToCRL(pair.getPrivate(), "SHA256WithRSAEncryption", deltaCrl3, certificateWithDelta);
+ stubCrlPoint("/crl1.crl", emptyX509CrlBytes);
+ stubCrlPoint("/deltaCrl", deltaCrlWithRevoked.getEncoded());
+ validationResult = certificateService.getCertificateValidationResult(certificateWithCrlDeltaEntity.getSecuredUuid());
+ Assertions.assertEquals(CertificateValidationStatus.REVOKED, validationResult.getValidationChecks().get(CertificateValidationCheck.CRL_VERIFICATION).getStatus());
+ Crl crlWithDelta2 = crlService.getCurrentCrl(certificateWithDelta, certificateWithDelta);
+ Assertions.assertNotNull(crlService.findCrlEntryForCertificate(certificateWithDelta.getSerialNumber().toString(16), crlWithDelta2.getUuid()));
+ }
+
+ private void stubCrlPoint(String urlPart, byte[] body) {
+ mockServer.stubFor(WireMock
+ .get(WireMock.urlPathMatching(urlPart))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("Content-Type", "application/json")
+ .withHeader("Set-Cookie", "session_id=91837492837")
+ .withHeader("Set-Cookie", "split_test_group=B")
+ .withHeader("Cache-Control", "no-cache")
+ .withBody(body)));
+ }
+
+ private X509Certificate createSelfSignedCertificateWithCrl(String commonName, List crlUrls, List deltaCrlUrls) throws CertIOException, NoSuchAlgorithmException, OperatorCreationException, CertificateException {
+ KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
+ keyPairGen.initialize(2048);
+ KeyPair pair = keyPairGen.generateKeyPair();
+ SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pair.getPublic().getEncoded());
+
+ X500Name x500Name = new X500Name("CN=" + commonName);
+ X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(x500Name, BigInteger.ONE, new Date(0), new Date(Long.MAX_VALUE), x500Name, subjectPublicKeyInfo);
+ CRLDistPoint crlDistPoint = new CRLDistPoint(createCrlDistributionPoints(crlUrls));
+ certificateBuilder.addExtension(Extension.cRLDistributionPoints, false, crlDistPoint);
+ if (deltaCrlUrls != null) {
+ CRLDistPoint deltaCrlDistPoint = new CRLDistPoint(createCrlDistributionPoints(deltaCrlUrls));
+ certificateBuilder.addExtension(Extension.freshestCRL, false, deltaCrlDistPoint);
+ }
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+ .build(pair.getPrivate());
+ X509CertificateHolder holder = certificateBuilder.build(signer);
+ JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
+ converter.setProvider(new BouncyCastleProvider());
+ return converter.getCertificate(holder);
+ }
+
+
private int getNumberOfCertificatesInPkcs7(String content) throws CMSException {
String pkcs7Data = new String(Base64.getDecoder().decode(content));
pkcs7Data = pkcs7Data.replace("-----BEGIN PKCS7-----", "").replace("-----END PKCS7-----", "").replaceAll("\\s", "");
@@ -175,5 +317,64 @@ private int getNumberOfCertificatesInPem(String content) throws IOException {
return pemObjects.size();
}
+ public static Date calculateDate(int hoursInFuture) {
+ long secs = System.currentTimeMillis() / 1000;
+ return new Date((secs + ((long) hoursInFuture * 60 * 60)) * 1000);
+ }
+
+ private X509CRL createEmptyCRL(X509Certificate caCert, PrivateKey caKey) throws CRLException, OperatorCreationException, CertIOException {
+ X509v2CRLBuilder crlGen = new X509v2CRLBuilder(X500Name.getInstance(caCert.getSubjectX500Principal().getEncoded()), calculateDate(0));
+ crlGen.setNextUpdate(calculateDate(24 * 7));
+ crlGen.addExtension(Extension.cRLNumber,
+ false,
+ new CRLNumber(BigInteger.ONE));
+
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+ .setProvider("BC").build(caKey);
+
+ JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider("BC");
+ return converter.getCRL(crlGen.build(signer));
+ }
+
+ private X509CRL createEmptyDeltaCRL(X509Certificate caCert, PrivateKey caKey, BigInteger deltaCrlIndicator, BigInteger deltaCrlNumber) throws CRLException, OperatorCreationException, CertIOException {
+ X509v2CRLBuilder crlGen = new X509v2CRLBuilder(X500Name.getInstance(caCert.getSubjectX500Principal().getEncoded()), calculateDate(0));
+ crlGen.setNextUpdate(calculateDate(24 * 7));
+ crlGen.addExtension(Extension.cRLNumber, false, new CRLNumber(deltaCrlNumber));
+ crlGen.addExtension(Extension.deltaCRLIndicator, false, new CRLNumber(deltaCrlIndicator));
+
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+ .setProvider("BC").build(caKey);
+
+ JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider("BC");
+ return converter.getCRL(crlGen.build(signer));
+ }
+
+ public X509CRL addRevocationToCRL(PrivateKey caKey, String sigAlg, X509CRL crl, X509Certificate certToRevoke) throws IOException, GeneralSecurityException, OperatorCreationException {
+ JcaX509v2CRLBuilder crlGen = new JcaX509v2CRLBuilder(crl);
+ crlGen.setNextUpdate(calculateDate(24 * 7));
+
+ // add revocation
+ ExtensionsGenerator extGen = new ExtensionsGenerator();
+ extGen.addExtension(Extension.reasonCode, false, org.bouncycastle.asn1.x509.CRLReason.lookup(2));
+ crlGen.addCRLEntry(certToRevoke.getSerialNumber(),
+ new Date(), extGen.generate());
+ ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
+ .setProvider("BC").build(caKey);
+ JcaX509CRLConverter converter = new JcaX509CRLConverter().setProvider("BC");
+
+ return converter.getCRL(crlGen.build(signer));
+ }
+
+ private DistributionPoint[] createCrlDistributionPoints(List urls) {
+ List list = new ArrayList<>();
+ for (String url : urls) {
+ DERIA5String deria5String = new DERIA5String(url);
+ GeneralName generalName = new GeneralName(GeneralName.uniformResourceIdentifier, deria5String);
+ DistributionPointName distributionPointName = new DistributionPointName(new GeneralNames(generalName));
+ list.add(new DistributionPoint(distributionPointName, null, null));
+ }
+ return list.toArray(new DistributionPoint[0]);
+ }
+
}
diff --git a/src/test/java/com/czertainly/core/service/ClientOperationServiceV2Test.java b/src/test/java/com/czertainly/core/service/ClientOperationServiceV2Test.java
index a50cb8381..57c57c00d 100644
--- a/src/test/java/com/czertainly/core/service/ClientOperationServiceV2Test.java
+++ b/src/test/java/com/czertainly/core/service/ClientOperationServiceV2Test.java
@@ -255,6 +255,7 @@ public void testRevokeCertificate() throws ConnectorException, CertificateExcept
.willReturn(WireMock.okJson("true")));
ClientCertificateRevocationDto request = new ClientCertificateRevocationDto();
+ request.setAttributes(List.of());
clientOperationService.revokeCertificateAction(certificate.getUuid(), request, true);
}
diff --git a/src/test/java/com/czertainly/core/service/RaProfileServiceTest.java b/src/test/java/com/czertainly/core/service/RaProfileServiceTest.java
index 2eb66f42e..531b22edb 100644
--- a/src/test/java/com/czertainly/core/service/RaProfileServiceTest.java
+++ b/src/test/java/com/czertainly/core/service/RaProfileServiceTest.java
@@ -6,17 +6,14 @@
import com.czertainly.api.exception.ValidationException;
import com.czertainly.api.model.client.approvalprofile.ApprovalProfileDto;
import com.czertainly.api.model.client.approvalprofile.ApprovalProfileRelationDto;
-import com.czertainly.api.model.client.approvalprofile.ApprovalProfileResponseDto;
import com.czertainly.api.model.client.raprofile.AddRaProfileRequestDto;
import com.czertainly.api.model.client.raprofile.EditRaProfileRequestDto;
import com.czertainly.api.model.common.NameAndUuidDto;
import com.czertainly.api.model.core.auth.Resource;
import com.czertainly.api.model.core.connector.ConnectorStatus;
import com.czertainly.api.model.core.raprofile.RaProfileDto;
-import com.czertainly.api.model.core.scheduler.PaginationRequestDto;
import com.czertainly.core.dao.entity.*;
import com.czertainly.core.dao.repository.*;
-import com.czertainly.core.security.authz.SecuredParentUUID;
import com.czertainly.core.security.authz.SecuredUUID;
import com.czertainly.core.security.authz.SecurityFilter;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -131,6 +128,13 @@ public void testAddRaProfile() throws ConnectorException, AlreadyExistException
mockServer.stubFor(WireMock
.get(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/raProfile/attributes"))
.willReturn(WireMock.okJson("[]")));
+ mockServer.stubFor(WireMock
+ .post(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/caCertificates"))
+ .willReturn(WireMock.okJson("""
+ {
+ "certificates": [
+ ]
+ }""")));
mockServer.stubFor(WireMock
.post(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/raProfile/attributes/validate"))
.willReturn(WireMock.okJson("true")));
@@ -280,4 +284,49 @@ public void testListOfApprovalProfilesByRAProfile() throws NotFoundException, Al
Assertions.assertEquals(approvalProfile.getUuid().toString(), approvalProfileDto.getUuid());
}
+ @Test
+ public void testGetAuthorityCertificateChain() throws ConnectorException, AlreadyExistException {
+ mockServer.stubFor(WireMock
+ .post(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/caCertificates"))
+ .willReturn(WireMock.okJson("""
+ {
+ "certificates": [
+ {
+ "certificateData": "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",
+ "uuid": null,
+ "meta": null,
+ "certificateType": "X.509"
+ } ]
+ }""")));
+ mockServer.stubFor(WireMock
+ .get(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/raProfile/attributes"))
+ .willReturn(WireMock.okJson("[]")));
+ mockServer.stubFor(WireMock
+ .post(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/raProfile/attributes/validate"))
+ .willReturn(WireMock.okJson("true")));
+
+ EditRaProfileRequestDto request = new EditRaProfileRequestDto();
+ request.setDescription("some description");
+ request.setAttributes(List.of());
+ raProfileService.editRaProfile(authorityInstanceReference.getSecuredParentUuid(), raProfile.getSecuredUuid(), request);
+ Assertions.assertNotNull(raProfile.getAuthorityCertificateUuid());
+
+ mockServer.stubFor(WireMock
+ .post(WireMock.urlPathMatching("/v1/authorityProvider/authorities/[^/]+/caCertificates"))
+ .willReturn(WireMock.okJson("""
+ {
+ "certificates": [
+ ]
+ }""")));
+ raProfileService.editRaProfile(authorityInstanceReference.getSecuredParentUuid(), raProfile.getSecuredUuid(), request);
+ Assertions.assertNull(raProfile.getAuthorityCertificateUuid());
+
+ AddRaProfileRequestDto requestAdd = new AddRaProfileRequestDto();
+ requestAdd.setName("testRaProfile2");
+ requestAdd.setAttributes(List.of());
+ RaProfileDto dto = raProfileService.addRaProfile(authorityInstanceReference.getSecuredParentUuid(), requestAdd);
+ Assertions.assertEquals(raProfile.getAuthorityCertificateUuid(), raProfileRepository.findByUuid(UUID.fromString(dto.getUuid())).get().getAuthorityCertificateUuid());
+
+ }
+
}