diff --git a/src/main/java/com/cisd/tms/modules/cert/controller/CertificateController.java b/src/main/java/com/cisd/tms/modules/cert/controller/CertificateController.java new file mode 100644 index 0000000..4e2c911 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/controller/CertificateController.java @@ -0,0 +1,57 @@ +package com.cisd.tms.modules.cert.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.cert.dto.CertificateDetailResponse; +import com.cisd.tms.modules.cert.dto.CertificateItemResponse; +import com.cisd.tms.modules.cert.dto.CertificateListRequest; +import com.cisd.tms.modules.cert.dto.IdRequest; +import com.cisd.tms.modules.cert.dto.ImportCertificateResponse; +import com.cisd.tms.modules.cert.service.CertificateService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/v1/certificates") +@Tag(name = "Certificates") +public class CertificateController { + + private final CertificateService certificateService; + + public CertificateController(CertificateService certificateService) { + this.certificateService = certificateService; + } + + @PostMapping("/list") + public ApiResponse> list(@Valid @RequestBody CertificateListRequest request) { + return ApiResponse.success(certificateService.list(request)); + } + + @PostMapping("/import") + public ApiResponse importCertificate( + @RequestPart("file") MultipartFile file, + @RequestParam("certType") String certType, + @RequestParam(value = "entityId", required = false) Long entityId + ) { + return ApiResponse.success(certificateService.importCertificate(file, certType, entityId)); + } + + @PostMapping("/detail") + public ApiResponse detail(@Valid @RequestBody IdRequest request) { + return ApiResponse.success(certificateService.detail(request.getId())); + } + + @PostMapping("/delete") + public ApiResponse delete(@Valid @RequestBody IdRequest request) { + certificateService.delete(request.getId()); + return ApiResponse.success(); + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/controller/EntityController.java b/src/main/java/com/cisd/tms/modules/cert/controller/EntityController.java new file mode 100644 index 0000000..73dd622 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/controller/EntityController.java @@ -0,0 +1,64 @@ +package com.cisd.tms.modules.cert.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.cert.dto.EntityCreateRequest; +import com.cisd.tms.modules.cert.dto.EntityCreateResponse; +import com.cisd.tms.modules.cert.dto.EntityGenP10Request; +import com.cisd.tms.modules.cert.dto.EntityItemResponse; +import com.cisd.tms.modules.cert.dto.EntityListRequest; +import com.cisd.tms.modules.cert.dto.EntityUpdateRequest; +import com.cisd.tms.modules.cert.dto.IdRequest; +import com.cisd.tms.modules.cert.dto.P10Response; +import com.cisd.tms.modules.cert.service.EntityService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/entities") +@Tag(name = "Cert Entities") +public class EntityController { + + private final EntityService entityService; + + public EntityController(EntityService entityService) { + this.entityService = entityService; + } + + @PostMapping("/list") + public ApiResponse> list(@Valid @RequestBody EntityListRequest request) { + return ApiResponse.success(entityService.list(request)); + } + + @PostMapping("/create") + public ApiResponse create(@Valid @RequestBody EntityCreateRequest request) { + return ApiResponse.success(entityService.create(request)); + } + + @PostMapping("/update") + public ApiResponse update(@Valid @RequestBody EntityUpdateRequest request) { + entityService.update(request); + return ApiResponse.success(); + } + + @PostMapping("/delete") + public ApiResponse delete(@Valid @RequestBody IdRequest request) { + entityService.delete(request.getId()); + return ApiResponse.success(); + } + + @PostMapping("/gen-p10") + public ApiResponse genP10(@Valid @RequestBody EntityGenP10Request request) { + return ApiResponse.success(new P10Response(entityService.generateP10(request))); + } + + @PostMapping("/p10") + public ApiResponse p10(@Valid @RequestBody IdRequest request) { + return ApiResponse.success(new P10Response(entityService.getP10(request.getId()))); + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/controller/TrustedCertController.java b/src/main/java/com/cisd/tms/modules/cert/controller/TrustedCertController.java new file mode 100644 index 0000000..79c39d0 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/controller/TrustedCertController.java @@ -0,0 +1,57 @@ +package com.cisd.tms.modules.cert.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.cert.dto.DeleteDependencyResponse; +import com.cisd.tms.modules.cert.dto.IdRequest; +import com.cisd.tms.modules.cert.dto.ImportCertificateResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertDeleteRequest; +import com.cisd.tms.modules.cert.dto.TrustedCertDetailResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertItemResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertListRequest; +import com.cisd.tms.modules.cert.service.TrustedCertService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/v1/trusted-certificates") +@Tag(name = "Trusted Certificates") +public class TrustedCertController { + + private final TrustedCertService trustedCertService; + + public TrustedCertController(TrustedCertService trustedCertService) { + this.trustedCertService = trustedCertService; + } + + @PostMapping("/list") + public ApiResponse> list(@Valid @RequestBody TrustedCertListRequest request) { + return ApiResponse.success(trustedCertService.list(request)); + } + + @PostMapping("/import") + public ApiResponse importTrusted( + @RequestPart("file") MultipartFile file, + @RequestParam("alias") String alias + ) { + return ApiResponse.success(trustedCertService.importTrusted(file, alias)); + } + + @PostMapping("/detail") + public ApiResponse detail(@Valid @RequestBody IdRequest request) { + return ApiResponse.success(trustedCertService.detail(request.getId())); + } + + @PostMapping("/delete") + public ApiResponse delete(@Valid @RequestBody TrustedCertDeleteRequest request) { + return ApiResponse.success(trustedCertService.delete(request)); + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CertificateDetailResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateDetailResponse.java new file mode 100644 index 0000000..2eaeaff --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateDetailResponse.java @@ -0,0 +1,22 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CertificateDetailResponse { + private Long id; + private Long entityId; + private String certType; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private String certData; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private String runtimeStatus; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CertificateItemResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateItemResponse.java new file mode 100644 index 0000000..cfd4dad --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateItemResponse.java @@ -0,0 +1,21 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CertificateItemResponse { + private Long id; + private Long entityId; + private String certType; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private String runtimeStatus; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CertificateListRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateListRequest.java new file mode 100644 index 0000000..c42ce5a --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CertificateListRequest.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CertificateListRequest extends CommonPageRequest { + private String certType; + private String keyword; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/CommonPageRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/CommonPageRequest.java new file mode 100644 index 0000000..7dd1d93 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/CommonPageRequest.java @@ -0,0 +1,13 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.constraints.Min; +import lombok.Data; + +@Data +public class CommonPageRequest { + @Min(1) + private int pageNum = 1; + @Min(1) + private int pageSize = 10; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/DeleteDependencyResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/DeleteDependencyResponse.java new file mode 100644 index 0000000..55876aa --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/DeleteDependencyResponse.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class DeleteDependencyResponse { + private long childCaCount; + private long issuedCertCount; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateRequest.java new file mode 100644 index 0000000..da7301a --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateRequest.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +@Data +public class EntityCreateRequest { + @NotBlank + private String entityName; + @NotBlank + private String algoType; + private List allowIps; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateResponse.java new file mode 100644 index 0000000..09d1f25 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityCreateResponse.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EntityCreateResponse { + private Long entityId; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityGenP10Request.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityGenP10Request.java new file mode 100644 index 0000000..c710ea3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityGenP10Request.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class EntityGenP10Request { + @NotNull + private Long id; + @Valid + @NotNull + private Subject subject; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityItemResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityItemResponse.java new file mode 100644 index 0000000..73e6750 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityItemResponse.java @@ -0,0 +1,19 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class EntityItemResponse { + private Long id; + private String entityName; + private String algoType; + private Integer keyLen; + private Integer keyIdx; + private String subject; + private List allowIps; + private LocalDateTime createTime; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityListRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityListRequest.java new file mode 100644 index 0000000..bf22f21 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityListRequest.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EntityListRequest extends CommonPageRequest { + private String keyword; + private String algoType; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/EntityUpdateRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/EntityUpdateRequest.java new file mode 100644 index 0000000..4825c76 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/EntityUpdateRequest.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class EntityUpdateRequest { + @NotNull + private Long id; + private String entityName; + private List allowIps; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/IdRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/IdRequest.java new file mode 100644 index 0000000..9ab5ffd --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/IdRequest.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class IdRequest { + @NotNull + private Long id; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/ImportCertificateResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/ImportCertificateResponse.java new file mode 100644 index 0000000..1bc2407 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/ImportCertificateResponse.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class ImportCertificateResponse { + private Long certId; + private String subjectDn; + private LocalDateTime validTo; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/P10Response.java b/src/main/java/com/cisd/tms/modules/cert/dto/P10Response.java new file mode 100644 index 0000000..8e0de1d --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/P10Response.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class P10Response { + private String p10; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java b/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java new file mode 100644 index 0000000..f2dd675 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java @@ -0,0 +1,72 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +@Data +public class Subject { + + private static final String COMMA = ","; + + private String commonName; + private String country; + private String province; + private String city; + private String org; + private String orgUnit; + + public String getDN() { + Assert.hasText(commonName, "commonName must not be blank"); + StringBuilder builder = new StringBuilder(); + if (StringUtils.hasText(commonName)) { + builder.append("CN=").append(commonName).append(COMMA); + } + if (StringUtils.hasText(orgUnit)) { + builder.append("OU=").append(orgUnit).append(COMMA); + } + if (StringUtils.hasText(org)) { + builder.append("O=").append(org).append(COMMA); + } + if (StringUtils.hasText(city)) { + builder.append("L=").append(city).append(COMMA); + } + if (StringUtils.hasText(province)) { + builder.append("ST=").append(province).append(COMMA); + } + if (StringUtils.hasText(country)) { + builder.append("C=").append(country).append(COMMA); + } + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } + + public static Subject fromDN(String dn) { + Assert.hasText(dn, "dn must not be blank"); + Subject subject = new Subject(); + String[] splits = dn.trim().split(","); + for (String split : splits) { + String trim = split.trim(); + if (trim.startsWith("C=")) { + subject.setCountry(trim.substring(2)); + } + if (trim.startsWith("ST=")) { + subject.setProvince(trim.substring(3)); + } + if (trim.startsWith("L=")) { + subject.setCity(trim.substring(2)); + } + if (trim.startsWith("O=")) { + subject.setOrg(trim.substring(2)); + } + if (trim.startsWith("OU=")) { + subject.setOrgUnit(trim.substring(3)); + } + if (trim.startsWith("CN=")) { + subject.setCommonName(trim.substring(3)); + } + } + Assert.hasText(subject.commonName, "commonName must not be blank"); + return subject; + } +} diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDeleteRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDeleteRequest.java new file mode 100644 index 0000000..09d92b8 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDeleteRequest.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.cert.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class TrustedCertDeleteRequest { + @NotNull + private Long id; + private Boolean force = Boolean.FALSE; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDetailResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDetailResponse.java new file mode 100644 index 0000000..62fbf16 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertDetailResponse.java @@ -0,0 +1,22 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class TrustedCertDetailResponse { + private Long id; + private String alias; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private String certData; + private Integer caLevel; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private LocalDateTime importTime; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertItemResponse.java b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertItemResponse.java new file mode 100644 index 0000000..290de4a --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertItemResponse.java @@ -0,0 +1,21 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class TrustedCertItemResponse { + private Long id; + private String alias; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private Integer caLevel; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private LocalDateTime importTime; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertListRequest.java b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertListRequest.java new file mode 100644 index 0000000..a6c5c0d --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/dto/TrustedCertListRequest.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.cert.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class TrustedCertListRequest extends CommonPageRequest { + private String keyword; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/CertificateEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/CertificateEntity.java new file mode 100644 index 0000000..35c9312 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/entity/CertificateEntity.java @@ -0,0 +1,31 @@ +package com.cisd.tms.modules.cert.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@TableName("t_certificate") +public class CertificateEntity extends BaseEntity { + + private Long entityId; + private String certType; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private String certData; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private LocalDateTime importTime; + private String signData; + private String createBy; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java new file mode 100644 index 0000000..2e04ca3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java @@ -0,0 +1,27 @@ +package com.cisd.tms.modules.cert.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@TableName("t_key_entity") +public class KeyEntity extends BaseEntity { + + private String entityName; + private String algoType; + private Integer keyLen; + private Integer keyIdx; + private String backupData; + private String publicKey; + private String p10Data; + private String subjectDn; + private String allowIps; + private String signData; + private String createBy; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/TrustedCertEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/TrustedCertEntity.java new file mode 100644 index 0000000..3b28072 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/entity/TrustedCertEntity.java @@ -0,0 +1,31 @@ +package com.cisd.tms.modules.cert.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@TableName("t_trusted_cert") +public class TrustedCertEntity extends BaseEntity { + + private String alias; + private String subjectDn; + private String issuerDn; + private String serialNumber; + private String algoType; + private String fingerprint; + private String certData; + private Integer caLevel; + private LocalDateTime validFrom; + private LocalDateTime validTo; + private LocalDateTime importTime; + private String signData; + private String createBy; +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateEntityMapper.java b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateEntityMapper.java new file mode 100644 index 0000000..f9b05ae --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/mapper/CertificateEntityMapper.java @@ -0,0 +1,10 @@ +package com.cisd.tms.modules.cert.mapper; + +import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; +import com.cisd.tms.modules.cert.entity.CertificateEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CertificateEntityMapper extends BaseMapperX { +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/mapper/KeyEntityMapper.java b/src/main/java/com/cisd/tms/modules/cert/mapper/KeyEntityMapper.java new file mode 100644 index 0000000..1694b14 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/mapper/KeyEntityMapper.java @@ -0,0 +1,10 @@ +package com.cisd.tms.modules.cert.mapper; + +import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KeyEntityMapper extends BaseMapperX { +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/mapper/TrustedCertEntityMapper.java b/src/main/java/com/cisd/tms/modules/cert/mapper/TrustedCertEntityMapper.java new file mode 100644 index 0000000..ab52235 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/mapper/TrustedCertEntityMapper.java @@ -0,0 +1,10 @@ +package com.cisd.tms.modules.cert.mapper; + +import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; +import com.cisd.tms.modules.cert.entity.TrustedCertEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface TrustedCertEntityMapper extends BaseMapperX { +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java new file mode 100644 index 0000000..b529fdd --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.cert.repository; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.CertificateListRequest; +import com.cisd.tms.modules.cert.entity.CertificateEntity; +import java.util.Optional; + +public interface CertificateRepository { + CertificateEntity save(CertificateEntity entity); + void deleteById(Long id); + Optional findById(Long id); + Optional findByFingerprint(String fingerprint); + boolean existsEntityCertByEntityId(Long entityId); + long countByIssuerDn(String issuerDn); + Page page(CertificateListRequest request); +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java new file mode 100644 index 0000000..9fab4e4 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/KeyEntityRepository.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.cert.repository; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.EntityListRequest; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import java.util.List; +import java.util.Optional; + +public interface KeyEntityRepository { + KeyEntity save(KeyEntity entity); + void deleteById(Long id); + Optional findById(Long id); + Optional findByKeyIdx(Integer keyIdx); + List findUsedKeyIdx(); + Page page(EntityListRequest request); +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/TrustedCertRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/TrustedCertRepository.java new file mode 100644 index 0000000..7ea2ac9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/TrustedCertRepository.java @@ -0,0 +1,18 @@ +package com.cisd.tms.modules.cert.repository; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.TrustedCertListRequest; +import com.cisd.tms.modules.cert.entity.TrustedCertEntity; +import java.util.List; +import java.util.Optional; + +public interface TrustedCertRepository { + TrustedCertEntity save(TrustedCertEntity entity); + void deleteById(Long id); + Optional findById(Long id); + Optional findByFingerprint(String fingerprint); + List findBySubjectDn(String subjectDn); + long countByIssuerDn(String issuerDn); + Page page(TrustedCertListRequest request); +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java new file mode 100644 index 0000000..d65ca7d --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java @@ -0,0 +1,86 @@ +package com.cisd.tms.modules.cert.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.CertificateListRequest; +import com.cisd.tms.modules.cert.entity.CertificateEntity; +import com.cisd.tms.modules.cert.mapper.CertificateEntityMapper; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Optional; + +@Repository +public class CertificateRepositoryImpl implements CertificateRepository { + + private final CertificateEntityMapper mapper; + + public CertificateRepositoryImpl(CertificateEntityMapper mapper) { + this.mapper = mapper; + } + + @Override + public CertificateEntity save(CertificateEntity entity) { + if (entity.getId() == null) { + mapper.insert(entity); + } else { + mapper.updateById(entity); + } + return entity; + } + + @Override + public void deleteById(Long id) { + mapper.deleteById(id); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(mapper.selectById(id)); + } + + @Override + public Optional findByFingerprint(String fingerprint) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CertificateEntity::getFingerprint, fingerprint) + .last("LIMIT 1"); + return Optional.ofNullable(mapper.selectOne(wrapper)); + } + + @Override + public boolean existsEntityCertByEntityId(Long entityId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CertificateEntity::getEntityId, entityId) + .eq(CertificateEntity::getCertType, "ENTITY"); + return mapper.selectCount(wrapper) > 0; + } + + @Override + public long countByIssuerDn(String issuerDn) { + return mapper.selectCount(new LambdaQueryWrapper() + .eq(CertificateEntity::getIssuerDn, issuerDn)); + } + + @Override + public Page page(CertificateListRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(request.getCertType())) { + wrapper.eq(CertificateEntity::getCertType, request.getCertType()); + } + if (StringUtils.hasText(request.getKeyword())) { + wrapper.and(w -> w.like(CertificateEntity::getSubjectDn, request.getKeyword()) + .or().like(CertificateEntity::getIssuerDn, request.getKeyword())); + } + wrapper.orderByDesc(CertificateEntity::getImportTime); + long total = mapper.selectCount(wrapper); + long offset = (long) (request.getPageNum() - 1) * request.getPageSize(); + wrapper.last("LIMIT " + offset + "," + request.getPageSize()); + List records = mapper.selectList(wrapper); + Page page = new Page<>(request.getPageNum(), request.getPageSize(), total); + page.setRecords(records); + return page; + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java new file mode 100644 index 0000000..4a11658 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/KeyEntityRepositoryImpl.java @@ -0,0 +1,82 @@ +package com.cisd.tms.modules.cert.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.EntityListRequest; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import com.cisd.tms.modules.cert.mapper.KeyEntityMapper; +import com.cisd.tms.modules.cert.repository.KeyEntityRepository; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Repository +public class KeyEntityRepositoryImpl implements KeyEntityRepository { + + private final KeyEntityMapper mapper; + + public KeyEntityRepositoryImpl(KeyEntityMapper mapper) { + this.mapper = mapper; + } + + @Override + public KeyEntity save(KeyEntity entity) { + if (entity.getId() == null) { + mapper.insert(entity); + } else { + mapper.updateById(entity); + } + return entity; + } + + @Override + public void deleteById(Long id) { + mapper.deleteById(id); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(mapper.selectById(id)); + } + + @Override + public Optional findByKeyIdx(Integer keyIdx) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(KeyEntity::getKeyIdx, keyIdx) + .last("LIMIT 1"); + return Optional.ofNullable(mapper.selectOne(wrapper)); + } + + @Override + public List findUsedKeyIdx() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .select(KeyEntity::getKeyIdx); + return mapper.selectList(wrapper).stream() + .map(KeyEntity::getKeyIdx) + .collect(Collectors.toList()); + } + + @Override + public Page page(EntityListRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(request.getKeyword())) { + wrapper.and(w -> w.like(KeyEntity::getEntityName, request.getKeyword()) + .or().like(KeyEntity::getSubjectDn, request.getKeyword())); + } + if (StringUtils.hasText(request.getAlgoType())) { + wrapper.eq(KeyEntity::getAlgoType, request.getAlgoType()); + } + wrapper.orderByDesc(KeyEntity::getCreateTime); + long total = mapper.selectCount(wrapper); + long offset = (long) (request.getPageNum() - 1) * request.getPageSize(); + wrapper.last("LIMIT " + offset + "," + request.getPageSize()); + List records = mapper.selectList(wrapper); + Page page = new Page<>(request.getPageNum(), request.getPageSize(), total); + page.setRecords(records); + return page; + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/TrustedCertRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/TrustedCertRepositoryImpl.java new file mode 100644 index 0000000..666fe33 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/TrustedCertRepositoryImpl.java @@ -0,0 +1,83 @@ +package com.cisd.tms.modules.cert.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.cert.dto.TrustedCertListRequest; +import com.cisd.tms.modules.cert.entity.TrustedCertEntity; +import com.cisd.tms.modules.cert.mapper.TrustedCertEntityMapper; +import com.cisd.tms.modules.cert.repository.TrustedCertRepository; +import org.springframework.stereotype.Repository; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Optional; + +@Repository +public class TrustedCertRepositoryImpl implements TrustedCertRepository { + + private final TrustedCertEntityMapper mapper; + + public TrustedCertRepositoryImpl(TrustedCertEntityMapper mapper) { + this.mapper = mapper; + } + + @Override + public TrustedCertEntity save(TrustedCertEntity entity) { + if (entity.getId() == null) { + mapper.insert(entity); + } else { + mapper.updateById(entity); + } + return entity; + } + + @Override + public void deleteById(Long id) { + mapper.deleteById(id); + } + + @Override + public Optional findById(Long id) { + return Optional.ofNullable(mapper.selectById(id)); + } + + @Override + public Optional findByFingerprint(String fingerprint) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(TrustedCertEntity::getFingerprint, fingerprint) + .last("LIMIT 1"); + return Optional.ofNullable(mapper.selectOne(wrapper)); + } + + @Override + public List findBySubjectDn(String subjectDn) { + return mapper.selectList(new LambdaQueryWrapper() + .eq(TrustedCertEntity::getSubjectDn, subjectDn) + .orderByAsc(TrustedCertEntity::getCaLevel)); + } + + @Override + public long countByIssuerDn(String issuerDn) { + return mapper.selectCount(new LambdaQueryWrapper() + .eq(TrustedCertEntity::getIssuerDn, issuerDn)); + } + + @Override + public Page page(TrustedCertListRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(request.getKeyword())) { + wrapper.and(w -> w.like(TrustedCertEntity::getAlias, request.getKeyword()) + .or().like(TrustedCertEntity::getSubjectDn, request.getKeyword()) + .or().like(TrustedCertEntity::getIssuerDn, request.getKeyword())); + } + wrapper.orderByDesc(TrustedCertEntity::getImportTime); + long total = mapper.selectCount(wrapper); + long offset = (long) (request.getPageNum() - 1) * request.getPageSize(); + wrapper.last("LIMIT " + offset + "," + request.getPageSize()); + List records = mapper.selectList(wrapper); + Page page = new Page<>(request.getPageNum(), request.getPageSize(), total); + page.setRecords(records); + return page; + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java new file mode 100644 index 0000000..6dcf7bc --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java @@ -0,0 +1,156 @@ +package com.cisd.tms.modules.cert.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.cert.dto.CertificateDetailResponse; +import com.cisd.tms.modules.cert.dto.CertificateItemResponse; +import com.cisd.tms.modules.cert.dto.CertificateListRequest; +import com.cisd.tms.modules.cert.dto.ImportCertificateResponse; +import com.cisd.tms.modules.cert.entity.CertificateEntity; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import com.cisd.tms.modules.cert.support.CertPemSupport; +import com.cisd.tms.modules.cert.support.CertRuntimeStatusResolver; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Base64; + +@Service +public class CertificateService { + + private final CertificateRepository certificateRepository; + private final EntityService entityService; + private final TrustedCertService trustedCertService; + + public CertificateService( + CertificateRepository certificateRepository, + EntityService entityService, + TrustedCertService trustedCertService + ) { + this.certificateRepository = certificateRepository; + this.entityService = entityService; + this.trustedCertService = trustedCertService; + } + + public Page list(CertificateListRequest request) { + Page page = certificateRepository.page(request); + Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(page.getRecords().stream().map(this::toItem).toList()); + return result; + } + + @Transactional + public ImportCertificateResponse importCertificate(MultipartFile file, String certType, Long entityId) { + try { + X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + assertValidTime(certificate); + String fingerprint = CertPemSupport.sha256Fingerprint(certificate); + if (certificateRepository.findByFingerprint(fingerprint).isPresent()) { + throw new BizException(ErrorCode.CONFLICT.getCode(), "certificate already exists"); + } + Long finalEntityId = null; + if ("ENTITY".equalsIgnoreCase(certType)) { + if (entityId == null) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "entityId is required for ENTITY cert"); + } + KeyEntity entity = entityService.getById(entityId); + byte[] certKey = certificate.getPublicKey().getEncoded(); + byte[] entityKey = Base64.getDecoder().decode(entity.getPublicKey()); + byte[] entityKeyX509 = com.cisd.tms.modules.cert.support.Sm2KeySupport.toSm2PublicKey(entityKey).getEncoded(); + if (!java.util.Arrays.equals(certKey, entityKeyX509)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate public key does not match entity"); + } + finalEntityId = entityId; + } + trustedCertService.validateParentChain(trustedCertService.resolveIssuer(certificate.getIssuerX500Principal().getName())); + certificate.verify( + CertPemSupport.parseCertificate( + trustedCertService.resolveIssuer(certificate.getIssuerX500Principal().getName()) + .getCertData().getBytes() + ).getPublicKey(), + "BC" + ); + CertificateEntity entity = new CertificateEntity() + .setEntityId(finalEntityId) + .setCertType(certType) + .setSubjectDn(certificate.getSubjectX500Principal().getName()) + .setIssuerDn(certificate.getIssuerX500Principal().getName()) + .setSerialNumber(certificate.getSerialNumber().toString(16).toUpperCase()) + .setAlgoType("SM2") + .setFingerprint(fingerprint) + .setCertData(CertPemSupport.toPem(certificate)) + .setValidFrom(LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault())) + .setValidTo(LocalDateTime.ofInstant(certificate.getNotAfter().toInstant(), ZoneId.systemDefault())) + .setImportTime(LocalDateTime.now()) + .setCreateBy("system"); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + certificateRepository.save(entity); + return new ImportCertificateResponse(entity.getId(), entity.getSubjectDn(), entity.getValidTo()); + } catch (BizException ex) { + throw ex; + } catch (Exception ex) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate import failed: " + ex.getMessage()); + } + } + + public CertificateDetailResponse detail(Long id) { + CertificateEntity entity = getById(id); + CertificateDetailResponse response = new CertificateDetailResponse(); + response.setId(entity.getId()); + response.setEntityId(entity.getEntityId()); + response.setCertType(entity.getCertType()); + response.setSubjectDn(entity.getSubjectDn()); + response.setIssuerDn(entity.getIssuerDn()); + response.setSerialNumber(entity.getSerialNumber()); + response.setAlgoType(entity.getAlgoType()); + response.setFingerprint(entity.getFingerprint()); + response.setCertData(entity.getCertData()); + response.setValidFrom(entity.getValidFrom()); + response.setValidTo(entity.getValidTo()); + response.setRuntimeStatus(CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo())); + return response; + } + + @Transactional + public void delete(Long id) { + getById(id); + certificateRepository.deleteById(id); + } + + private CertificateItemResponse toItem(CertificateEntity entity) { + CertificateItemResponse response = new CertificateItemResponse(); + response.setId(entity.getId()); + response.setEntityId(entity.getEntityId()); + response.setCertType(entity.getCertType()); + response.setSubjectDn(entity.getSubjectDn()); + response.setIssuerDn(entity.getIssuerDn()); + response.setSerialNumber(entity.getSerialNumber()); + response.setAlgoType(entity.getAlgoType()); + response.setFingerprint(entity.getFingerprint()); + response.setValidFrom(entity.getValidFrom()); + response.setValidTo(entity.getValidTo()); + response.setRuntimeStatus(CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo())); + return response; + } + + private CertificateEntity getById(Long id) { + return certificateRepository.findById(id) + .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate not found")); + } + + private void assertValidTime(X509Certificate certificate) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime notBefore = LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault()); + LocalDateTime notAfter = LocalDateTime.ofInstant(certificate.getNotAfter().toInstant(), ZoneId.systemDefault()); + if (now.isBefore(notBefore) || now.isAfter(notAfter)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate is not in valid time window"); + } + } +} diff --git a/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java new file mode 100644 index 0000000..038137d --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java @@ -0,0 +1,238 @@ +package com.cisd.tms.modules.cert.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; +import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; +import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest; +import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.cert.dto.EntityCreateRequest; +import com.cisd.tms.modules.cert.dto.EntityCreateResponse; +import com.cisd.tms.modules.cert.dto.EntityGenP10Request; +import com.cisd.tms.modules.cert.dto.EntityItemResponse; +import com.cisd.tms.modules.cert.dto.EntityListRequest; +import com.cisd.tms.modules.cert.dto.EntityUpdateRequest; +import com.cisd.tms.modules.cert.dto.Subject; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import com.cisd.tms.modules.cert.repository.KeyEntityRepository; +import com.cisd.tms.modules.cert.support.Sm2KeySupport; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.bouncycastle.asn1.gm.GMObjectIdentifiers; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Service +public class EntityService { + + private static final String SM2 = "SM2"; + + private final KeyEntityRepository keyEntityRepository; + private final CertificateRepository certificateRepository; + private final PcieCryptoService pcieCryptoService; + private final ObjectMapper objectMapper; + + public EntityService( + KeyEntityRepository keyEntityRepository, + CertificateRepository certificateRepository, + PcieCryptoService pcieCryptoService, + ObjectMapper objectMapper + ) { + this.keyEntityRepository = keyEntityRepository; + this.certificateRepository = certificateRepository; + this.pcieCryptoService = pcieCryptoService; + this.objectMapper = objectMapper; + } + + public Page list(EntityListRequest request) { + Page page = keyEntityRepository.page(request); + Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(page.getRecords().stream().map(this::toItem).toList()); + return result; + } + + @Transactional + public EntityCreateResponse create(EntityCreateRequest request) { + if (!SM2.equalsIgnoreCase(request.getAlgoType())) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "only SM2 is supported"); + } + int keyIdx = resolveNextKeyIdx(); + GenerateKeyPairRequest generateRequest = new GenerateKeyPairRequest(); + generateRequest.setKeyBits(256); + generateRequest.setKeyIndex(keyIdx); + generateRequest.setKeyType(PcieUserKeyType.SIGN.getCode()); + + pcieCryptoService.generateKeyPairEcc(generateRequest); + try { + BackupDataResult backup = pcieCryptoService.backupUserKey(keyIdx, PcieUserKeyType.SIGN.getCode(), 4096); + BackupDataResult publicKey = pcieCryptoService.exportSignPublicKeyEcc(keyIdx); + KeyEntity entity = new KeyEntity() + .setEntityName(request.getEntityName()) + .setAlgoType(SM2) + .setKeyLen(256) + .setKeyIdx(keyIdx) + .setBackupData(Base64.getEncoder().encodeToString(backup.getData())) + .setPublicKey(Base64.getEncoder().encodeToString(publicKey.getData())) + .setAllowIps(writeAllowIps(request.getAllowIps())) + .setCreateBy("system"); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + keyEntityRepository.save(entity); + return new EntityCreateResponse(entity.getId()); + } catch (RuntimeException ex) { + try { + pcieCryptoService.deleteUserKey(keyIdx); + } catch (Exception ignored) { + } + throw ex; + } + } + + @Transactional + public void update(EntityUpdateRequest request) { + KeyEntity entity = getById(request.getId()); + if (StringUtils.hasText(request.getEntityName())) { + entity.setEntityName(request.getEntityName()); + } + if (request.getAllowIps() != null) { + entity.setAllowIps(writeAllowIps(request.getAllowIps())); + } + keyEntityRepository.save(entity); + } + + @Transactional + public void delete(Long id) { + KeyEntity entity = getById(id); + if (certificateRepository.existsEntityCertByEntityId(id)) { + throw new BizException(ErrorCode.CONFLICT.getCode(), "entity certificate exists, delete certificate first"); + } + pcieCryptoService.deleteUserKey(entity.getKeyIdx()); + keyEntityRepository.deleteById(id); + } + + @Transactional + public String generateP10(EntityGenP10Request request) { + KeyEntity entity = getById(request.getId()); + Subject subject = request.getSubject(); + String subjectDn = subject.getDN(); + PublicKey publicKey = Sm2KeySupport.toSm2PublicKey(Base64.getDecoder().decode(entity.getPublicKey())); + PKCS10CertificationRequestBuilder builder = + new JcaPKCS10CertificationRequestBuilder(new X500Name(subjectDn), publicKey); + PKCS10CertificationRequest csr = builder.build(new CardContentSigner(entity.getKeyIdx(), pcieCryptoService)); + String pem = encodePkcs10Pem(csr); + entity.setSubjectDn(subjectDn); + entity.setP10Data(pem); + keyEntityRepository.save(entity); + return pem; + } + + public String getP10(Long id) { + KeyEntity entity = getById(id); + return entity.getP10Data(); + } + + public KeyEntity getById(Long id) { + return keyEntityRepository.findById(id) + .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "entity not found")); + } + + private EntityItemResponse toItem(KeyEntity entity) { + EntityItemResponse response = new EntityItemResponse(); + response.setId(entity.getId()); + response.setEntityName(entity.getEntityName()); + response.setAlgoType(entity.getAlgoType()); + response.setKeyLen(entity.getKeyLen()); + response.setKeyIdx(entity.getKeyIdx()); + response.setSubject(entity.getSubjectDn()); + response.setAllowIps(readAllowIps(entity.getAllowIps())); + response.setCreateTime(entity.getCreateTime()); + return response; + } + + private int resolveNextKeyIdx() { + List used = keyEntityRepository.findUsedKeyIdx(); + for (int idx = 2; idx <= 8; idx++) { + if (!used.contains(idx)) { + return idx; + } + } + throw new BizException(ErrorCode.CONFLICT.getCode(), "no available key index in [2..8]"); + } + + private String writeAllowIps(List allowIps) { + try { + return allowIps == null ? "[]" : objectMapper.writeValueAsString(allowIps); + } catch (JsonProcessingException e) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "invalid allowIps"); + } + } + + private List readAllowIps(String allowIps) { + if (!StringUtils.hasText(allowIps)) { + return List.of(); + } + try { + return objectMapper.readValue(allowIps, new TypeReference>() {}); + } catch (Exception e) { + return new ArrayList<>(); + } + } + + private String encodePkcs10Pem(PKCS10CertificationRequest csr) { + try { + String base64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)) + .encodeToString(csr.getEncoded()); + return "-----BEGIN CERTIFICATE REQUEST-----\n" + base64 + "\n-----END CERTIFICATE REQUEST-----"; + } catch (Exception e) { + throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "failed to encode csr"); + } + } + + private static final class CardContentSigner implements ContentSigner { + + private final java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream(); + private final int keyIdx; + private final PcieCryptoService pcieCryptoService; + + private CardContentSigner(int keyIdx, PcieCryptoService pcieCryptoService) { + this.keyIdx = keyIdx; + this.pcieCryptoService = pcieCryptoService; + } + + @Override + public org.bouncycastle.asn1.x509.AlgorithmIdentifier getAlgorithmIdentifier() { + return new org.bouncycastle.asn1.x509.AlgorithmIdentifier(GMObjectIdentifiers.sm2sign_with_sm3); + } + + @Override + public OutputStream getOutputStream() { + return output; + } + + @Override + public byte[] getSignature() { + UserKeySignRequest request = new UserKeySignRequest(); + request.setKeyIndex(keyIdx); + request.setData(output.toByteArray()); + return Sm2KeySupport.rawSignatureToDer(pcieCryptoService.userKeySignWithSm3(request).getData()); + } + } +} diff --git a/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java new file mode 100644 index 0000000..c0642d2 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java @@ -0,0 +1,184 @@ +package com.cisd.tms.modules.cert.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.cert.dto.DeleteDependencyResponse; +import com.cisd.tms.modules.cert.dto.ImportCertificateResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertDeleteRequest; +import com.cisd.tms.modules.cert.dto.TrustedCertDetailResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertItemResponse; +import com.cisd.tms.modules.cert.dto.TrustedCertListRequest; +import com.cisd.tms.modules.cert.entity.TrustedCertEntity; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import com.cisd.tms.modules.cert.repository.TrustedCertRepository; +import com.cisd.tms.modules.cert.support.CertPemSupport; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.security.Security; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; + +@Service +public class TrustedCertService { + + static { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private final TrustedCertRepository trustedCertRepository; + private final CertificateRepository certificateRepository; + + public TrustedCertService( + TrustedCertRepository trustedCertRepository, + CertificateRepository certificateRepository + ) { + this.trustedCertRepository = trustedCertRepository; + this.certificateRepository = certificateRepository; + } + + public Page list(TrustedCertListRequest request) { + Page page = trustedCertRepository.page(request); + Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(page.getRecords().stream().map(this::toItem).toList()); + return result; + } + + @Transactional + public ImportCertificateResponse importTrusted(MultipartFile file, String alias) { + try { + X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + assertValidTime(certificate); + String fingerprint = CertPemSupport.sha256Fingerprint(certificate); + if (trustedCertRepository.findByFingerprint(fingerprint).isPresent()) { + throw new BizException(ErrorCode.CONFLICT.getCode(), "trusted certificate already exists"); + } + int caLevel = isSelfSigned(certificate) ? 0 : 1; + if (caLevel == 0) { + certificate.verify(certificate.getPublicKey(), "BC"); + } else { + TrustedCertEntity issuer = resolveIssuer(certificate.getIssuerX500Principal().getName()); + X509Certificate issuerCert = CertPemSupport.parseCertificate(issuer.getCertData().getBytes()); + certificate.verify(issuerCert.getPublicKey(), "BC"); + validateParentChain(issuer); + } + TrustedCertEntity entity = new TrustedCertEntity() + .setAlias(alias) + .setSubjectDn(certificate.getSubjectX500Principal().getName()) + .setIssuerDn(certificate.getIssuerX500Principal().getName()) + .setSerialNumber(certificate.getSerialNumber().toString(16).toUpperCase()) + .setAlgoType("SM2") + .setFingerprint(fingerprint) + .setCertData(CertPemSupport.toPem(certificate)) + .setCaLevel(caLevel) + .setValidFrom(LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault())) + .setValidTo(LocalDateTime.ofInstant(certificate.getNotAfter().toInstant(), ZoneId.systemDefault())) + .setImportTime(LocalDateTime.now()) + .setCreateBy("system"); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + trustedCertRepository.save(entity); + return new ImportCertificateResponse(entity.getId(), entity.getSubjectDn(), entity.getValidTo()); + } catch (BizException ex) { + throw ex; + } catch (Exception ex) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "trusted certificate import failed: " + ex.getMessage()); + } + } + + public TrustedCertDetailResponse detail(Long id) { + TrustedCertEntity entity = getById(id); + TrustedCertDetailResponse response = new TrustedCertDetailResponse(); + response.setId(entity.getId()); + response.setAlias(entity.getAlias()); + response.setSubjectDn(entity.getSubjectDn()); + response.setIssuerDn(entity.getIssuerDn()); + response.setSerialNumber(entity.getSerialNumber()); + response.setAlgoType(entity.getAlgoType()); + response.setFingerprint(entity.getFingerprint()); + response.setCertData(entity.getCertData()); + response.setCaLevel(entity.getCaLevel()); + response.setValidFrom(entity.getValidFrom()); + response.setValidTo(entity.getValidTo()); + response.setImportTime(entity.getImportTime()); + return response; + } + + @Transactional + public DeleteDependencyResponse delete(TrustedCertDeleteRequest request) { + TrustedCertEntity entity = getById(request.getId()); + long childCaCount = trustedCertRepository.countByIssuerDn(entity.getSubjectDn()); + long issuedCertCount = certificateRepository.countByIssuerDn(entity.getSubjectDn()); + if ((childCaCount > 0 || issuedCertCount > 0) && !Boolean.TRUE.equals(request.getForce())) { + throw new BizException(ErrorCode.CONFLICT.getCode(), "trusted certificate has dependent certificates"); + } + trustedCertRepository.deleteById(request.getId()); + return new DeleteDependencyResponse(childCaCount, issuedCertCount); + } + + public TrustedCertEntity resolveIssuer(String subjectDn) { + List matched = trustedCertRepository.findBySubjectDn(subjectDn); + if (matched.isEmpty()) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "issuer certificate not found"); + } + return matched.get(0); + } + + public void validateParentChain(TrustedCertEntity entity) { + if (entity.getSubjectDn().equals(entity.getIssuerDn())) { + return; + } + try { + X509Certificate certificate = CertPemSupport.parseCertificate(entity.getCertData().getBytes()); + TrustedCertEntity issuer = resolveIssuer(entity.getIssuerDn()); + X509Certificate issuerCert = CertPemSupport.parseCertificate(issuer.getCertData().getBytes()); + certificate.verify(issuerCert.getPublicKey(), "BC"); + validateParentChain(issuer); + } catch (BizException ex) { + throw ex; + } catch (Exception ex) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate chain validation failed"); + } + } + + private TrustedCertItemResponse toItem(TrustedCertEntity entity) { + TrustedCertItemResponse response = new TrustedCertItemResponse(); + response.setId(entity.getId()); + response.setAlias(entity.getAlias()); + response.setSubjectDn(entity.getSubjectDn()); + response.setIssuerDn(entity.getIssuerDn()); + response.setSerialNumber(entity.getSerialNumber()); + response.setAlgoType(entity.getAlgoType()); + response.setFingerprint(entity.getFingerprint()); + response.setCaLevel(entity.getCaLevel()); + response.setValidFrom(entity.getValidFrom()); + response.setValidTo(entity.getValidTo()); + response.setImportTime(entity.getImportTime()); + return response; + } + + private TrustedCertEntity getById(Long id) { + return trustedCertRepository.findById(id) + .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "trusted certificate not found")); + } + + private void assertValidTime(X509Certificate certificate) { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime notBefore = LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault()); + LocalDateTime notAfter = LocalDateTime.ofInstant(certificate.getNotAfter().toInstant(), ZoneId.systemDefault()); + if (now.isBefore(notBefore) || now.isAfter(notAfter)) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate is not in valid time window"); + } + } + + private boolean isSelfSigned(X509Certificate certificate) { + return certificate.getSubjectX500Principal().equals(certificate.getIssuerX500Principal()); + } +} diff --git a/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java b/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java new file mode 100644 index 0000000..903b142 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/support/CertPemSupport.java @@ -0,0 +1,75 @@ +package com.cisd.tms.modules.cert.support; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; + +public final class CertPemSupport { + + private static final String PROVIDER = "BC"; + + static { + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private CertPemSupport() { + } + + public static X509Certificate parseCertificate(byte[] bytes) { + try { + byte[] normalized = toDer(bytes); + CertificateFactory factory = CertificateFactory.getInstance("X.509", PROVIDER); + return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(normalized)); + } catch (Exception e) { + throw new IllegalArgumentException("invalid certificate file", e); + } + } + + public static String toPem(X509Certificate certificate) { + try { + String base64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)) + .encodeToString(certificate.getEncoded()); + return "-----BEGIN CERTIFICATE-----\n" + base64 + "\n-----END CERTIFICATE-----"; + } catch (Exception e) { + throw new IllegalArgumentException("failed to encode certificate pem", e); + } + } + + public static String sha256Fingerprint(X509Certificate certificate) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return hex(digest.digest(certificate.getEncoded())); + } catch (Exception e) { + throw new IllegalArgumentException("failed to calculate fingerprint", e); + } + } + + private static byte[] toDer(byte[] bytes) { + String text = new String(bytes, StandardCharsets.UTF_8); + if (!text.contains("BEGIN CERTIFICATE")) { + return bytes; + } + String normalized = text + .replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s+", ""); + return Base64.getDecoder().decode(normalized); + } + + private static String hex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + builder.append(String.format("%02X", b)); + } + return builder.toString(); + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/support/CertRuntimeStatusResolver.java b/src/main/java/com/cisd/tms/modules/cert/support/CertRuntimeStatusResolver.java new file mode 100644 index 0000000..c2dfcb3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/support/CertRuntimeStatusResolver.java @@ -0,0 +1,21 @@ +package com.cisd.tms.modules.cert.support; + +import java.time.LocalDateTime; + +public final class CertRuntimeStatusResolver { + + private CertRuntimeStatusResolver() { + } + + public static String resolve(LocalDateTime validFrom, LocalDateTime validTo) { + LocalDateTime now = LocalDateTime.now(); + if (now.isBefore(validFrom)) { + return "NOT_YET_VALID"; + } + if (now.isAfter(validTo)) { + return "EXPIRED"; + } + return "VALID"; + } +} + diff --git a/src/main/java/com/cisd/tms/modules/cert/support/Sm2KeySupport.java b/src/main/java/com/cisd/tms/modules/cert/support/Sm2KeySupport.java new file mode 100644 index 0000000..3568bf0 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/support/Sm2KeySupport.java @@ -0,0 +1,84 @@ +package com.cisd.tms.modules.cert.support; + +import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey; +import com.cisd.tms.integration.crypto.pcie.jna.EccSignature; +import com.sun.jna.Pointer; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.Security; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.util.Arrays; + +public final class Sm2KeySupport { + + private static final String PROVIDER = "BC"; + + static { + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private Sm2KeySupport() { + } + + public static PublicKey toSm2PublicKey(byte[] blob) { + try { + EccRefPublicKey key = new EccRefPublicKey(); + Pointer pointer = key.getPointer(); + pointer.clear(key.size()); + pointer.write(0, blob, 0, blob.length); + key.read(); + byte[] x = trimCoordinate(key.x); + byte[] y = trimCoordinate(key.y); + ECNamedCurveParameterSpec bcSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); + ECNamedCurveSpec spec = new ECNamedCurveSpec("sm2p256v1", bcSpec.getCurve(), bcSpec.getG(), bcSpec.getN()); + ECPublicKeySpec publicKeySpec = new ECPublicKeySpec( + new ECPoint(new BigInteger(1, x), new BigInteger(1, y)), + spec + ); + return KeyFactory.getInstance("EC", PROVIDER).generatePublic(publicKeySpec); + } catch (Exception e) { + throw new IllegalArgumentException("failed to parse sm2 public key", e); + } + } + + public static byte[] rawSignatureToDer(byte[] rawSignature) { + EccSignature signature = new EccSignature(); + Pointer pointer = signature.getPointer(); + pointer.clear(signature.size()); + pointer.write(0, rawSignature, 0, rawSignature.length); + signature.read(); + ASN1EncodableVector vector = new ASN1EncodableVector(); + vector.add(new ASN1Integer(new BigInteger(1, trimCoordinate(signature.r)))); + vector.add(new ASN1Integer(new BigInteger(1, trimCoordinate(signature.s)))); + try { + return new DERSequence(vector).getEncoded(); + } catch (Exception e) { + throw new IllegalArgumentException("failed to encode sm2 signature", e); + } + } + + private static byte[] trimCoordinate(byte[] source) { + int start = 0; + while (start < source.length - 1 && source[start] == 0) { + start++; + } + byte[] trimmed = Arrays.copyOfRange(source, start, source.length); + if (trimmed.length > 32) { + return Arrays.copyOfRange(trimmed, trimmed.length - 32, trimmed.length); + } + return trimmed; + } +} + diff --git a/src/main/resources/db/migration/V7__add_cert_module_tables.sql b/src/main/resources/db/migration/V7__add_cert_module_tables.sql new file mode 100644 index 0000000..ec4cf13 --- /dev/null +++ b/src/main/resources/db/migration/V7__add_cert_module_tables.sql @@ -0,0 +1,72 @@ +-- ----------------------------------------------------------------------------- +-- Cert module tables +-- ----------------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS t_key_entity ( + id BIGINT PRIMARY KEY, + entity_name VARCHAR(255) NOT NULL, + algo_type VARCHAR(16) NOT NULL COMMENT 'SM2', + key_len INT NOT NULL COMMENT 'SM2 fixed 256 in V1', + key_idx INT NOT NULL COMMENT 'card key index, allowed range [2..8]', + backup_data VARCHAR(500) NOT NULL COMMENT 'SDFE_BackupUserKey output', + public_key VARCHAR(500) NOT NULL COMMENT 'public key, Base64/PEM', + p10_data VARCHAR(2000) NULL COMMENT 'PKCS#10 PEM content', + subject_dn VARCHAR(500) NULL COMMENT 'DN used when generating P10', + allow_ips VARCHAR(500) NULL COMMENT 'allowed source IP list', + sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1', + create_by VARCHAR(64) NULL, + create_time DATETIME(3) NOT NULL, + update_time DATETIME(3) NOT NULL, + UNIQUE KEY uk_t_key_entity_key_idx (key_idx), + KEY idx_t_key_entity_name (entity_name), + KEY idx_t_key_entity_algo (algo_type), + KEY idx_t_key_entity_create_time (create_time) +); + +CREATE TABLE IF NOT EXISTS t_certificate ( + id BIGINT PRIMARY KEY, + entity_id BIGINT NULL COMMENT 'required when cert_type=ENTITY', + cert_type VARCHAR(16) NOT NULL COMMENT 'ENTITY / USER', + subject_dn VARCHAR(500) NOT NULL, + issuer_dn VARCHAR(1024) NOT NULL, + serial_number VARCHAR(128) NOT NULL, + algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1', + fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint', + cert_data VARCHAR(2000) NOT NULL COMMENT 'certificate PEM content', + valid_from DATETIME(3) NOT NULL, + valid_to DATETIME(3) NOT NULL, + import_time DATETIME(3) NOT NULL, + sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1', + create_by VARCHAR(64) NULL, + create_time DATETIME(3) NOT NULL, + update_time DATETIME(3) NOT NULL, + UNIQUE KEY uk_t_certificate_fingerprint (fingerprint), + KEY idx_t_certificate_entity_id (entity_id), + KEY idx_t_certificate_cert_type (cert_type), + KEY idx_t_certificate_issuer_dn (issuer_dn), + KEY idx_t_certificate_valid_to (valid_to), + KEY idx_t_certificate_import_time (import_time) +); + +CREATE TABLE IF NOT EXISTS t_trusted_cert ( + id BIGINT PRIMARY KEY, + alias VARCHAR(128) NOT NULL, + subject_dn VARCHAR(1024) NOT NULL, + issuer_dn VARCHAR(1024) NOT NULL, + serial_number VARCHAR(128) NOT NULL, + algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1', + fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint', + cert_data VARCHAR(2000) NOT NULL COMMENT 'certificate PEM content', + ca_level TINYINT NOT NULL COMMENT '0:Root, 1:Intermediate', + valid_from DATETIME(3) NOT NULL, + valid_to DATETIME(3) NOT NULL, + import_time DATETIME(3) NOT NULL, + sign_data VARCHAR(512) NULL COMMENT 'reserved, not used in V1', + create_by VARCHAR(64) NULL, + create_time DATETIME(3) NOT NULL, + update_time DATETIME(3) NOT NULL, + UNIQUE KEY uk_t_trusted_cert_fingerprint (fingerprint), + KEY idx_t_trusted_cert_subject_dn (subject_dn), + KEY idx_t_trusted_cert_issuer_dn (issuer_dn), + KEY idx_t_trusted_cert_valid_to (valid_to) +);