feat:吊销列表、证书管理功能
This commit is contained in:
+14
-1
@@ -3,6 +3,7 @@ package com.cisd.tms.modules.auth.security;
|
||||
import com.cisd.tms.common.api.ApiResponse;
|
||||
import com.cisd.tms.common.enums.ErrorCode;
|
||||
import com.cisd.tms.common.util.HttpResponseUtil;
|
||||
import com.cisd.tms.modules.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.log.service.OperationAuditService;
|
||||
import com.cisd.tms.security.internal.InternalApiAuthInterceptor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -41,7 +42,7 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
return false;
|
||||
}
|
||||
String currentAuthLevel = (String) request.getAttribute(InternalApiAuthInterceptor.ATTR_AUTH_LEVEL);
|
||||
if (!requireInternalAuth.authLevel().name().equals(currentAuthLevel)) {
|
||||
if (!authLevelAllowed(currentAuthLevel, requireInternalAuth.authLevel())) {
|
||||
writeForbidden(response, "auth level not allowed");
|
||||
operationAuditService.recordDeniedLog(request, handlerMethod, "auth level not allowed");
|
||||
return false;
|
||||
@@ -51,6 +52,18 @@ public class InternalAuthorizationInterceptor implements HandlerInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean authLevelAllowed(String currentAuthLevel, AuthLevel requiredAuthLevel) {
|
||||
if (currentAuthLevel == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
AuthLevel current = AuthLevel.valueOf(currentAuthLevel);
|
||||
return current.ordinal() >= requiredAuthLevel.ordinal();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends java.lang.annotation.Annotation> T findAnnotation(HandlerMethod handlerMethod, Class<T> annotationType) {
|
||||
T methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getMethod(), annotationType);
|
||||
if (methodAnnotation != null) {
|
||||
|
||||
@@ -2,25 +2,33 @@ 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.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
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 com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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")
|
||||
@Tag(name = "证书管理", description = "实体证书和用户证书的导入、查询、详情和删除接口")
|
||||
public class CertificateController {
|
||||
|
||||
private final CertificateService certificateService;
|
||||
@@ -30,28 +38,38 @@ public class CertificateController {
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询证书列表", description = "按机构号、算法类型、证书类型和证书主题分页查询实体/用户证书,返回证书有效期、运行状态和指纹信息。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<Page<CertificateItemResponse>> list(@Valid @RequestBody CertificateListRequest request) {
|
||||
return ApiResponse.success(certificateService.list(request));
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入证书", description = "导入 PEM 或 DER 格式的实体/用户证书。系统会校验证书有效期、证书链、KeyUsage、CRL 吊销状态,并根据公钥匹配结果识别实体证书或用户证书。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.IMPORT, summary = "导入证书")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<ImportCertificateResponse> importCertificate(
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("certType") String certType,
|
||||
@RequestParam(value = "entityId", required = false) Long entityId
|
||||
@Parameter(description = "证书文件,支持 PEM/DER X.509 证书")
|
||||
@RequestPart("file") MultipartFile file
|
||||
) {
|
||||
return ApiResponse.success(certificateService.importCertificate(file, certType, entityId));
|
||||
return ApiResponse.success(certificateService.importCertificate(file));
|
||||
}
|
||||
|
||||
@PostMapping("/detail")
|
||||
@Operation(summary = "查询证书详情", description = "根据证书 ID 查询证书详情,包含证书 PEM 内容、DN、序列号、算法、有效期和运行状态。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<CertificateDetailResponse> detail(@Valid @RequestBody IdRequest request) {
|
||||
return ApiResponse.success(certificateService.detail(request.getId()));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除证书", description = "根据证书 ID 删除实体/用户证书记录。该操作需要 KEY_ADMIN FULL 会话并启用重放保护和审计。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.DELETE, summary = "删除证书")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<Void> delete(@Valid @RequestBody IdRequest request) {
|
||||
certificateService.delete(request.getId());
|
||||
return ApiResponse.success();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.cert.dto.CrlListRequest;
|
||||
import com.cisd.tms.modules.cert.dto.CrlRevokedItemResponse;
|
||||
import com.cisd.tms.modules.cert.dto.IdRequest;
|
||||
import com.cisd.tms.modules.cert.service.CrlService;
|
||||
import com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/crls")
|
||||
@Tag(name = "CRL管理", description = "证书吊销列表导入、吊销明细查询和 CRL 删除接口")
|
||||
public class CrlController {
|
||||
|
||||
private final CrlService crlService;
|
||||
|
||||
public CrlController(CrlService crlService) {
|
||||
this.crlService = crlService;
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入CRL", description = "导入 PEM 或 DER 格式的 X.509 CRL。系统会校验 CRL 时间窗口、重复指纹、签发者可信 CA、KeyUsage.cRLSign 和 CRL 签名。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.IMPORT, summary = "导入证书吊销列表")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<Void> importCrl(
|
||||
@Parameter(description = "CRL 文件,支持 PEM/DER X.509 CRL")
|
||||
@RequestPart("file") MultipartFile file
|
||||
) {
|
||||
crlService.importCrl(file);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询CRL吊销明细", description = "按算法类型和证书主题分页查询已导入 CRL 中的吊销证书明细,返回签发者、序列号、吊销原因和吊销时间。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<Page<CrlRevokedItemResponse>> list(@Valid @RequestBody CrlListRequest request) {
|
||||
return ApiResponse.success(crlService.list(request));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除CRL", description = "根据 CRL ID 删除 CRL 元数据及其吊销明细。删除后对应吊销记录不再参与证书运行状态判断。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.DELETE, summary = "删除证书吊销列表")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<Void> delete(@Valid @RequestBody IdRequest request) {
|
||||
crlService.delete(request.getId());
|
||||
return ApiResponse.success();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ 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.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.cert.dto.EntityCreateRequest;
|
||||
import com.cisd.tms.modules.cert.dto.EntityCreateResponse;
|
||||
import com.cisd.tms.modules.cert.dto.EntityGenP10Request;
|
||||
@@ -11,6 +14,11 @@ 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 com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -20,7 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/entities")
|
||||
@Tag(name = "Cert Entities")
|
||||
@Tag(name = "证书实体管理", description = "证书实体密钥生成、机构信息维护、P10 生成和 P10 查询接口")
|
||||
public class EntityController {
|
||||
|
||||
private final EntityService entityService;
|
||||
@@ -30,35 +38,54 @@ public class EntityController {
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询证书实体列表", description = "按机构号、算法类型和实体状态分页查询证书实体,返回密钥索引、证书主题、允许访问 IP 和状态信息。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<Page<EntityItemResponse>> list(@Valid @RequestBody EntityListRequest request) {
|
||||
return ApiResponse.success(entityService.list(request));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建证书实体", description = "为机构创建证书实体并在密码卡中生成签名密钥对。支持 SM2/RSA,当前 P10 生成仅支持 SM2 实体。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.CREATE, summary = "创建证书实体")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<EntityCreateResponse> create(@Valid @RequestBody EntityCreateRequest request) {
|
||||
return ApiResponse.success(entityService.create(request));
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
@Operation(summary = "更新证书实体", description = "更新证书实体的机构号、机构名称和允许访问 IP 列表,不修改算法类型、密钥索引或密钥材料。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.UPDATE, summary = "更新证书实体")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<Void> update(@Valid @RequestBody EntityUpdateRequest request) {
|
||||
entityService.update(request);
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除证书实体", description = "删除证书实体并删除密码卡中的对应用户密钥。若实体已绑定证书,需要先删除证书。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.DELETE, summary = "删除证书实体")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<Void> delete(@Valid @RequestBody IdRequest request) {
|
||||
entityService.delete(request.getId());
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@PostMapping("/gen-p10")
|
||||
@Operation(summary = "生成P10证书请求", description = "使用实体签名密钥生成 PKCS#10 证书请求,并保存证书主题、P10 内容和证书模式。当前仅支持 SM2。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.EXECUTE, summary = "生成证书请求")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<P10Response> genP10(@Valid @RequestBody EntityGenP10Request request) {
|
||||
return ApiResponse.success(new P10Response(entityService.generateP10(request)));
|
||||
}
|
||||
|
||||
@PostMapping("/p10")
|
||||
@Operation(summary = "查询P10证书请求", description = "根据实体 ID 查询已生成的 PKCS#10 PEM 内容。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<P10Response> p10(@Valid @RequestBody IdRequest request) {
|
||||
return ApiResponse.success(new P10Response(entityService.getP10(request.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ 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.auth.enums.AuthLevel;
|
||||
import com.cisd.tms.modules.auth.enums.RoleCode;
|
||||
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
|
||||
import com.cisd.tms.modules.cert.dto.DeleteDependencyResponse;
|
||||
import com.cisd.tms.modules.cert.dto.IdRequest;
|
||||
import com.cisd.tms.modules.cert.dto.ImportCertificateResponse;
|
||||
@@ -10,19 +13,24 @@ 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 com.cisd.tms.modules.log.annotation.AuditedOperation;
|
||||
import com.cisd.tms.modules.log.enums.ActionType;
|
||||
import com.cisd.tms.modules.log.enums.ModuleCode;
|
||||
import com.cisd.tms.security.internal.ReplayProtected;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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")
|
||||
@Tag(name = "可信CA证书管理", description = "可信根 CA 和中间 CA 证书的导入、查询、详情和删除接口")
|
||||
public class TrustedCertController {
|
||||
|
||||
private final TrustedCertService trustedCertService;
|
||||
@@ -32,26 +40,37 @@ public class TrustedCertController {
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "查询可信CA证书列表", description = "按算法类型和证书主题分页查询可信 CA 证书,返回 CA 层级、有效期和指纹信息。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<Page<TrustedCertItemResponse>> list(@Valid @RequestBody TrustedCertListRequest request) {
|
||||
return ApiResponse.success(trustedCertService.list(request));
|
||||
}
|
||||
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入可信CA证书", description = "导入 PEM 或 DER 格式的可信 CA 证书。系统会校验证书有效期、CA 属性、KeyUsage.keyCertSign,以及非根 CA 的签发链。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.IMPORT, summary = "导入可信证书")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<ImportCertificateResponse> importTrusted(
|
||||
@RequestPart("file") MultipartFile file,
|
||||
@RequestParam("alias") String alias
|
||||
@Parameter(description = "可信 CA 证书文件,支持 PEM/DER X.509 证书")
|
||||
@RequestPart("file") MultipartFile file
|
||||
) {
|
||||
return ApiResponse.success(trustedCertService.importTrusted(file, alias));
|
||||
return ApiResponse.success(trustedCertService.importTrusted(file, null));
|
||||
}
|
||||
|
||||
@PostMapping("/detail")
|
||||
@Operation(summary = "查询可信CA证书详情", description = "根据证书 ID 查询可信 CA 证书详情,包含证书 PEM 内容、DN、序列号、算法、CA 层级和有效期。")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.LIMITED)
|
||||
public ApiResponse<TrustedCertDetailResponse> detail(@Valid @RequestBody IdRequest request) {
|
||||
return ApiResponse.success(trustedCertService.detail(request.getId()));
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除可信CA证书", description = "删除可信 CA 证书。存在下级 CA 或已签发本地证书时,默认返回依赖数量;传入 force=true 时执行强制删除。")
|
||||
// @ReplayProtected
|
||||
@AuditedOperation(module = ModuleCode.KEY, action = ActionType.DELETE, summary = "删除可信证书")
|
||||
@RequireInternalAuth(role = RoleCode.KEY_ADMIN, authLevel = AuthLevel.FULL)
|
||||
public ApiResponse<DeleteDependencyResponse> delete(@Valid @RequestBody TrustedCertDeleteRequest request) {
|
||||
return ApiResponse.success(trustedCertService.delete(request));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "证书详情响应")
|
||||
public class CertificateDetailResponse {
|
||||
@Schema(description = "证书 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "关联证书实体 ID;用户证书为空", example = "10")
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "证书类型,ENTITY 表示实体证书,USER 表示用户证书", example = "ENTITY")
|
||||
private String certType;
|
||||
|
||||
@Schema(description = "证书主题 DN", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "签发者 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "证书序列号,十六进制大写", example = "19DDD25B8D0")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书 SHA-256 指纹,十六进制大写", example = "083C476C26DFD8876E0580601EA66726DF06BB20989DFD862BB34B9DD8B68094")
|
||||
private String fingerprint;
|
||||
|
||||
@Schema(description = "证书 PEM 内容")
|
||||
private String certData;
|
||||
|
||||
@Schema(description = "证书生效时间", example = "2026-04-30T00:00:00")
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
@Schema(description = "证书失效时间", example = "2027-04-30T00:00:00")
|
||||
private LocalDateTime validTo;
|
||||
|
||||
@Schema(description = "运行状态,VALID/NOT_YET_VALID/EXPIRED/REVOKED", example = "VALID")
|
||||
private String runtimeStatus;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "证书列表项")
|
||||
public class CertificateItemResponse {
|
||||
@Schema(description = "证书 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "关联证书实体 ID;用户证书为空", example = "10")
|
||||
private Long entityId;
|
||||
|
||||
@Schema(description = "关联证书实体机构号;用户证书为空", example = "00000001")
|
||||
private String orgCode;
|
||||
|
||||
@Schema(description = "证书类型,ENTITY 表示实体证书,USER 表示用户证书", example = "ENTITY")
|
||||
private String certType;
|
||||
|
||||
@Schema(description = "证书类型中文名称", example = "实体证书")
|
||||
private String certTypeName;
|
||||
|
||||
@Schema(description = "证书主题 DN", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "签发者 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "证书序列号,十六进制大写", example = "19DDD25B8D0")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书 SHA-256 指纹,十六进制大写", example = "083C476C26DFD8876E0580601EA66726DF06BB20989DFD862BB34B9DD8B68094")
|
||||
private String fingerprint;
|
||||
|
||||
@Schema(description = "证书生效时间", example = "2026-04-30T00:00:00")
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
@Schema(description = "证书失效时间", example = "2027-04-30T00:00:00")
|
||||
private LocalDateTime validTo;
|
||||
|
||||
@Schema(description = "导入时间", example = "2026-04-30T15:44:45")
|
||||
private LocalDateTime importTime;
|
||||
|
||||
@Schema(description = "运行状态,VALID/NOT_YET_VALID/EXPIRED/REVOKED", example = "VALID")
|
||||
private String runtimeStatus;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "证书列表查询请求")
|
||||
public class CertificateListRequest extends CommonPageRequest {
|
||||
private String certType;
|
||||
private String keyword;
|
||||
}
|
||||
@Schema(description = "机构号,按证书实体机构号模糊匹配;仅实体证书有关联机构号", example = "00000001")
|
||||
private String orgCode;
|
||||
|
||||
@Schema(description = "算法类型,支持 SM2/RSA", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书类型,ENTITY 表示实体证书,USER 表示用户证书", example = "ENTITY")
|
||||
private String certType;
|
||||
|
||||
@Schema(description = "证书主题 DN 模糊查询关键字", example = "CN=C")
|
||||
private String subject;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "通用分页请求")
|
||||
public class CommonPageRequest {
|
||||
@Min(1)
|
||||
@Schema(description = "页码,从 1 开始", example = "1")
|
||||
@Min(value = 1, message = "页码必须大于等于1")
|
||||
private int pageNum = 1;
|
||||
@Min(1)
|
||||
|
||||
@Schema(description = "每页数量", example = "10")
|
||||
@Min(value = 1, message = "每页数量必须大于等于1")
|
||||
private int pageSize = 10;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "CRL吊销明细列表查询请求")
|
||||
public class CrlListRequest extends CommonPageRequest {
|
||||
@Schema(description = "算法类型,支持 SM2/RSA", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "被吊销证书主题 DN 模糊查询关键字", example = "CN=C")
|
||||
private String subject;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "证书吊销信息")
|
||||
public class CrlRevocationInfo {
|
||||
@Schema(description = "CRL 签发者 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "被吊销证书序列号,十六进制大写", example = "19DDD25B8D0")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "被吊销证书主题 DN", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "CRL 编号扩展值", example = "123")
|
||||
private String crlNumber;
|
||||
|
||||
@Schema(description = "吊销原因", example = "keyCompromise")
|
||||
private String revocationReason;
|
||||
|
||||
@Schema(description = "吊销时间", example = "2026-04-30T15:00:00")
|
||||
private LocalDateTime revocationTime;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "CRL吊销明细列表项")
|
||||
public class CrlRevokedItemResponse {
|
||||
@Schema(description = "吊销明细 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "被吊销证书主题 DN;本地尚未导入证书时可能为空", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "CRL 签发者 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "被吊销证书序列号,十六进制大写", example = "19DDD25B8D0")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "CRL 编号扩展值", example = "123")
|
||||
private String crlNumber;
|
||||
|
||||
@Schema(description = "吊销原因", example = "keyCompromise")
|
||||
private String revocationReason;
|
||||
|
||||
@Schema(description = "吊销时间", example = "2026-04-30T15:00:00")
|
||||
private LocalDateTime revocationTime;
|
||||
|
||||
@Schema(description = "CRL 导入/发布时间", example = "2026-04-30T15:30:00")
|
||||
private LocalDateTime publishTime;
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "删除依赖统计响应")
|
||||
public class DeleteDependencyResponse {
|
||||
@Schema(description = "依赖该 CA 的下级 CA 数量", example = "0")
|
||||
private long childCaCount;
|
||||
|
||||
@Schema(description = "该 CA 已签发的本地证书数量", example = "1")
|
||||
private long issuedCertCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "创建证书实体请求")
|
||||
public class EntityCreateRequest {
|
||||
@NotBlank
|
||||
@Schema(description = "机构代码", example = "00000001")
|
||||
@NotBlank(message = "机构代码不能为空")
|
||||
private String orgCode;
|
||||
@NotBlank
|
||||
|
||||
@Schema(description = "机构名称", example = "测试机构")
|
||||
@NotBlank(message = "机构名称不能为空")
|
||||
private String orgName;
|
||||
@NotBlank
|
||||
|
||||
@Schema(description = "算法类型,支持 SM2/RSA", example = "SM2")
|
||||
@NotBlank(message = "算法类型不能为空")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "允许访问该实体证书能力的来源 IP 列表", example = "[\"127.0.0.1\"]")
|
||||
private List<String> allowIps;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "创建证书实体响应")
|
||||
public class EntityCreateResponse {
|
||||
@Schema(description = "创建后的证书实体 ID", example = "1")
|
||||
private Long entityId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "生成 P10 证书请求")
|
||||
public class EntityGenP10Request {
|
||||
@NotNull
|
||||
@Schema(description = "证书实体 ID", example = "1")
|
||||
@NotNull(message = "实体ID不能为空")
|
||||
private Long id;
|
||||
@NotNull
|
||||
|
||||
@Schema(description = "证书模式,SINGLE 表示单证,DOUBLE 表示双证", example = "SINGLE")
|
||||
@NotNull(message = "证书模式不能为空")
|
||||
private String certMode;
|
||||
|
||||
@Schema(description = "证书主题信息")
|
||||
@Valid
|
||||
@NotNull
|
||||
@NotNull(message = "证书主题不能为空")
|
||||
private Subject subject;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,44 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "证书实体列表项")
|
||||
public class EntityItemResponse {
|
||||
@Schema(description = "证书实体 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "机构代码", example = "00000001")
|
||||
private String orgCode;
|
||||
|
||||
@Schema(description = "机构名称", example = "测试机构")
|
||||
private String orgName;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "密钥长度,SM2 为 256,RSA 通常为 2048", example = "256")
|
||||
private Integer keyLen;
|
||||
|
||||
@Schema(description = "密码卡用户密钥索引", example = "2")
|
||||
private Integer keyIdx;
|
||||
|
||||
@Schema(description = "已生成 P10 的证书主题 DN", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subject;
|
||||
|
||||
@Schema(description = "允许访问该实体证书能力的来源 IP 列表", example = "[\"127.0.0.1\"]")
|
||||
private List<String> allowIps;
|
||||
|
||||
@Schema(description = "实体状态,CREATED/P10_GENERATED/CERT_IMPORTED", example = "CREATED")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "实体状态中文名称", example = "已创建")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "创建时间", example = "2026-04-30T15:30:00")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "证书实体列表查询请求")
|
||||
public class EntityListRequest extends CommonPageRequest {
|
||||
@Schema(description = "机构代码,模糊匹配", example = "00000001")
|
||||
private String orgCode;
|
||||
|
||||
@Schema(description = "算法类型,支持 SM2/RSA", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "实体状态,CREATED/P10_GENERATED/CERT_IMPORTED", example = "CREATED")
|
||||
private String status;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "更新证书实体请求")
|
||||
public class EntityUpdateRequest {
|
||||
@NotNull
|
||||
@Schema(description = "证书实体 ID", example = "1")
|
||||
@NotNull(message = "实体ID不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "机构代码", example = "00000001")
|
||||
private String orgCode;
|
||||
|
||||
@Schema(description = "机构名称", example = "测试机构")
|
||||
private String orgName;
|
||||
|
||||
@Schema(description = "允许访问该实体证书能力的来源 IP 列表", example = "[\"127.0.0.1\"]")
|
||||
private List<String> allowIps;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "通用 ID 请求")
|
||||
public class IdRequest {
|
||||
@NotNull
|
||||
@Schema(description = "记录 ID", example = "1")
|
||||
@NotNull(message = "ID不能为空")
|
||||
private Long id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -7,9 +8,14 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "证书导入响应")
|
||||
public class ImportCertificateResponse {
|
||||
@Schema(description = "导入后的证书 ID", example = "1")
|
||||
private Long certId;
|
||||
|
||||
@Schema(description = "证书主题 DN", example = "C=CN,O=CISD,CN=entity")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "证书失效时间", example = "2027-04-30T00:00:00")
|
||||
private LocalDateTime validTo;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "P10 证书请求响应")
|
||||
public class P10Response {
|
||||
@Schema(description = "PKCS#10 PEM 内容")
|
||||
private String p10;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
||||
@@ -8,13 +9,25 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Data
|
||||
@Schema(description = "证书主题信息")
|
||||
public class Subject {
|
||||
|
||||
@Schema(description = "通用名称 CN", example = "entity")
|
||||
private String commonName;
|
||||
|
||||
@Schema(description = "国家代码 C,必须为 2 位", example = "CN")
|
||||
private String country;
|
||||
|
||||
@Schema(description = "省份 ST", example = "浙江省")
|
||||
private String province;
|
||||
|
||||
@Schema(description = "城市 L", example = "杭州市")
|
||||
private String city;
|
||||
|
||||
@Schema(description = "组织 O", example = "CISD")
|
||||
private String org;
|
||||
|
||||
@Schema(description = "组织单元 OU", example = "Cert")
|
||||
private String orgUnit;
|
||||
|
||||
public String getDN() {
|
||||
@@ -22,7 +35,7 @@ public class Subject {
|
||||
}
|
||||
|
||||
public X500Name toX500Name() {
|
||||
Assert.hasText(commonName, "commonName must not be blank");
|
||||
Assert.hasText(commonName, "通用名称不能为空");
|
||||
X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
|
||||
if (StringUtils.hasText(commonName)) {
|
||||
builder.addRDN(BCStyle.CN, commonName);
|
||||
@@ -40,14 +53,14 @@ public class Subject {
|
||||
builder.addRDN(BCStyle.ST, province);
|
||||
}
|
||||
if (StringUtils.hasText(country)) {
|
||||
Assert.isTrue(country.length() == 2, "country must be a 2-letter country code");
|
||||
Assert.isTrue(country.length() == 2, "国家代码必须为2位");
|
||||
builder.addRDN(BCStyle.C, country);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static Subject fromDN(String dn) {
|
||||
Assert.hasText(dn, "dn must not be blank");
|
||||
Assert.hasText(dn, "DN不能为空");
|
||||
Subject subject = new Subject();
|
||||
String[] splits = dn.trim().split(",");
|
||||
for (String split : splits) {
|
||||
@@ -71,7 +84,7 @@ public class Subject {
|
||||
subject.setCommonName(trim.substring(3));
|
||||
}
|
||||
}
|
||||
Assert.hasText(subject.commonName, "commonName must not be blank");
|
||||
Assert.hasText(subject.commonName, "通用名称不能为空");
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "删除可信CA证书请求")
|
||||
public class TrustedCertDeleteRequest {
|
||||
@NotNull
|
||||
@Schema(description = "可信 CA 证书 ID", example = "1")
|
||||
@NotNull(message = "可信CA证书ID不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "是否强制删除;存在下级 CA 或已签发证书时需要显式传 true", example = "false")
|
||||
private Boolean force = Boolean.FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,46 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "可信CA证书详情响应")
|
||||
public class TrustedCertDetailResponse {
|
||||
@Schema(description = "可信 CA 证书 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "证书别名", example = "middle-ca")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "证书主题 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "签发者 DN", example = "C=CN,O=GMSSL,CN=RootCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "证书序列号,十六进制大写", example = "1001")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书 SHA-256 指纹,十六进制大写")
|
||||
private String fingerprint;
|
||||
|
||||
@Schema(description = "证书 PEM 内容")
|
||||
private String certData;
|
||||
|
||||
@Schema(description = "CA 层级,0 表示根 CA,1 表示中间 CA", example = "1")
|
||||
private Integer caLevel;
|
||||
|
||||
@Schema(description = "证书生效时间", example = "2026-04-30T00:00:00")
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
@Schema(description = "证书失效时间", example = "2036-04-30T00:00:00")
|
||||
private LocalDateTime validTo;
|
||||
|
||||
@Schema(description = "导入时间", example = "2026-04-30T15:30:00")
|
||||
private LocalDateTime importTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Schema(description = "可信CA证书列表项")
|
||||
public class TrustedCertItemResponse {
|
||||
@Schema(description = "可信 CA 证书 ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "证书别名", example = "middle-ca")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "证书主题 DN", example = "C=CN,O=GMSSL,CN=MiddleCA for Test")
|
||||
private String subjectDn;
|
||||
|
||||
@Schema(description = "签发者 DN", example = "C=CN,O=GMSSL,CN=RootCA for Test")
|
||||
private String issuerDn;
|
||||
|
||||
@Schema(description = "证书序列号,十六进制大写", example = "1001")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "算法类型", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书 SHA-256 指纹,十六进制大写")
|
||||
private String fingerprint;
|
||||
|
||||
@Schema(description = "CA 层级,0 表示根 CA,1 表示中间 CA", example = "1")
|
||||
private Integer caLevel;
|
||||
|
||||
@Schema(description = "证书生效时间", example = "2026-04-30T00:00:00")
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
@Schema(description = "证书失效时间", example = "2036-04-30T00:00:00")
|
||||
private LocalDateTime validTo;
|
||||
|
||||
@Schema(description = "导入时间", example = "2026-04-30T15:30:00")
|
||||
private LocalDateTime importTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.cisd.tms.modules.cert.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "可信CA证书列表查询请求")
|
||||
public class TrustedCertListRequest extends CommonPageRequest {
|
||||
private String keyword;
|
||||
}
|
||||
@Schema(description = "算法类型,支持 SM2/RSA", example = "SM2")
|
||||
private String algoType;
|
||||
|
||||
@Schema(description = "证书主题 DN 模糊查询关键字", example = "MiddleCA")
|
||||
private String subject;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_certificate_crl")
|
||||
public class CertificateCrlEntity extends BaseEntity {
|
||||
|
||||
private String issuerDn;
|
||||
private String crlNumber;
|
||||
private String algoType;
|
||||
private String fingerprint;
|
||||
private String crlData;
|
||||
private LocalDateTime thisUpdate;
|
||||
private LocalDateTime nextUpdate;
|
||||
private Integer revokedCount;
|
||||
private LocalDateTime importTime;
|
||||
private String createBy;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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_crl_revoked")
|
||||
public class CertificateCrlRevokedEntity extends BaseEntity {
|
||||
|
||||
private Long crlId;
|
||||
private String issuerDn;
|
||||
private String serialNumber;
|
||||
private String subjectDn;
|
||||
private String algoType;
|
||||
private String crlNumber;
|
||||
private LocalDateTime revocationTime;
|
||||
private String revocationReason;
|
||||
}
|
||||
@@ -29,6 +29,6 @@ public enum EntityCertMode {
|
||||
return Arrays.stream(values())
|
||||
.filter(value -> value.code.equalsIgnoreCase(code))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "invalid entity cert mode"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体证书模式无效"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,6 @@ public enum EntityStatus {
|
||||
return Arrays.stream(values())
|
||||
.filter(value -> value.code.equalsIgnoreCase(code))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "invalid entity status"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体状态无效"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cisd.tms.modules.cert.mapper;
|
||||
|
||||
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CertificateCrlEntityMapper extends BaseMapperX<CertificateCrlEntity> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cisd.tms.modules.cert.mapper;
|
||||
|
||||
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface CertificateCrlRevokedEntityMapper extends BaseMapperX<CertificateCrlRevokedEntity> {
|
||||
}
|
||||
@@ -10,8 +10,8 @@ public interface CertificateRepository {
|
||||
void deleteById(Long id);
|
||||
Optional<CertificateEntity> findById(Long id);
|
||||
Optional<CertificateEntity> findByFingerprint(String fingerprint);
|
||||
Optional<CertificateEntity> findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber);
|
||||
boolean existsEntityCertByEntityId(Long entityId);
|
||||
long countByIssuerDn(String issuerDn);
|
||||
Page<CertificateEntity> page(CertificateListRequest request);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.cisd.tms.modules.cert.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.cisd.tms.modules.cert.dto.CrlListRequest;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlEntity;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CrlRepository {
|
||||
CertificateCrlEntity saveCrl(CertificateCrlEntity entity);
|
||||
CertificateCrlRevokedEntity saveRevoked(CertificateCrlRevokedEntity entity);
|
||||
Optional<CertificateCrlEntity> findCrlById(Long id);
|
||||
Optional<CertificateCrlEntity> findCrlByFingerprint(String fingerprint);
|
||||
void deleteCrlById(Long id);
|
||||
void deleteRevokedByCrlId(Long crlId);
|
||||
Page<CertificateCrlRevokedEntity> pageRevoked(CrlListRequest request);
|
||||
boolean existsRevoked(String issuerDn, String serialNumber);
|
||||
Optional<CertificateCrlRevokedEntity> findRevocation(String issuerDn, String serialNumber);
|
||||
void updateRevokedSubjectIfMissing(String issuerDn, String serialNumber, String subjectDn, String algoType);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public interface KeyEntityRepository {
|
||||
void deleteById(Long id);
|
||||
Optional<KeyEntity> findById(Long id);
|
||||
Optional<KeyEntity> findByKeyIdx(Integer keyIdx);
|
||||
List<KeyEntity> findAll();
|
||||
List<Integer> findUsedKeyIdx();
|
||||
Page<KeyEntity> page(EntityListRequest request);
|
||||
void updateStatus(Long id, String status);
|
||||
|
||||
@@ -13,6 +13,6 @@ public interface TrustedCertRepository {
|
||||
Optional<TrustedCertEntity> findByFingerprint(String fingerprint);
|
||||
List<TrustedCertEntity> findBySubjectDn(String subjectDn);
|
||||
long countByIssuerDn(String issuerDn);
|
||||
long countByIssuerDnExcludingId(String issuerDn, Long excludedId);
|
||||
Page<TrustedCertEntity> page(TrustedCertListRequest request);
|
||||
}
|
||||
|
||||
|
||||
+22
-6
@@ -49,6 +49,15 @@ public class CertificateRepositoryImpl implements CertificateRepository {
|
||||
return Optional.ofNullable(mapper.selectOne(wrapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CertificateEntity> findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber) {
|
||||
LambdaQueryWrapper<CertificateEntity> wrapper = new LambdaQueryWrapper<CertificateEntity>()
|
||||
.eq(CertificateEntity::getIssuerDn, issuerDn)
|
||||
.eq(CertificateEntity::getSerialNumber, serialNumber)
|
||||
.last("LIMIT 1");
|
||||
return Optional.ofNullable(mapper.selectOne(wrapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsEntityCertByEntityId(Long entityId) {
|
||||
LambdaQueryWrapper<CertificateEntity> wrapper = new LambdaQueryWrapper<CertificateEntity>()
|
||||
@@ -66,12 +75,20 @@ public class CertificateRepositoryImpl implements CertificateRepository {
|
||||
@Override
|
||||
public Page<CertificateEntity> page(CertificateListRequest request) {
|
||||
LambdaQueryWrapper<CertificateEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(request.getCertType())) {
|
||||
wrapper.eq(CertificateEntity::getCertType, request.getCertType());
|
||||
if (StringUtils.hasText(request.getOrgCode())) {
|
||||
wrapper.apply(
|
||||
"entity_id IN (SELECT id FROM t_key_entity WHERE org_code LIKE CONCAT('%', {0}, '%'))",
|
||||
request.getOrgCode()
|
||||
);
|
||||
}
|
||||
if (StringUtils.hasText(request.getKeyword())) {
|
||||
wrapper.and(w -> w.like(CertificateEntity::getSubjectDn, request.getKeyword())
|
||||
.or().like(CertificateEntity::getIssuerDn, request.getKeyword()));
|
||||
if (StringUtils.hasText(request.getAlgoType())) {
|
||||
wrapper.eq(CertificateEntity::getAlgoType, request.getAlgoType().toUpperCase());
|
||||
}
|
||||
if (StringUtils.hasText(request.getCertType())) {
|
||||
wrapper.eq(CertificateEntity::getCertType, request.getCertType().toUpperCase());
|
||||
}
|
||||
if (StringUtils.hasText(request.getSubject())) {
|
||||
wrapper.like(CertificateEntity::getSubjectDn, request.getSubject());
|
||||
}
|
||||
wrapper.orderByDesc(CertificateEntity::getImportTime);
|
||||
long total = mapper.selectCount(wrapper);
|
||||
@@ -83,4 +100,3 @@ public class CertificateRepositoryImpl implements CertificateRepository {
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.cisd.tms.modules.cert.repository.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.cisd.tms.modules.cert.dto.CrlListRequest;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlEntity;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity;
|
||||
import com.cisd.tms.modules.cert.mapper.CertificateCrlEntityMapper;
|
||||
import com.cisd.tms.modules.cert.mapper.CertificateCrlRevokedEntityMapper;
|
||||
import com.cisd.tms.modules.cert.repository.CrlRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class CrlRepositoryImpl implements CrlRepository {
|
||||
|
||||
private final CertificateCrlEntityMapper crlMapper;
|
||||
private final CertificateCrlRevokedEntityMapper revokedMapper;
|
||||
|
||||
public CrlRepositoryImpl(CertificateCrlEntityMapper crlMapper, CertificateCrlRevokedEntityMapper revokedMapper) {
|
||||
this.crlMapper = crlMapper;
|
||||
this.revokedMapper = revokedMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CertificateCrlEntity saveCrl(CertificateCrlEntity entity) {
|
||||
if (entity.getId() == null) {
|
||||
crlMapper.insert(entity);
|
||||
} else {
|
||||
crlMapper.updateById(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CertificateCrlRevokedEntity saveRevoked(CertificateCrlRevokedEntity entity) {
|
||||
if (entity.getId() == null) {
|
||||
revokedMapper.insert(entity);
|
||||
} else {
|
||||
revokedMapper.updateById(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CertificateCrlEntity> findCrlById(Long id) {
|
||||
return Optional.ofNullable(crlMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CertificateCrlEntity> findCrlByFingerprint(String fingerprint) {
|
||||
return Optional.ofNullable(crlMapper.selectOne(new LambdaQueryWrapper<CertificateCrlEntity>()
|
||||
.eq(CertificateCrlEntity::getFingerprint, fingerprint)
|
||||
.last("LIMIT 1")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteCrlById(Long id) {
|
||||
crlMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRevokedByCrlId(Long crlId) {
|
||||
revokedMapper.delete(new LambdaQueryWrapper<CertificateCrlRevokedEntity>()
|
||||
.eq(CertificateCrlRevokedEntity::getCrlId, crlId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<CertificateCrlRevokedEntity> pageRevoked(CrlListRequest request) {
|
||||
LambdaQueryWrapper<CertificateCrlRevokedEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(request.getAlgoType())) {
|
||||
wrapper.eq(CertificateCrlRevokedEntity::getAlgoType, request.getAlgoType().toUpperCase());
|
||||
}
|
||||
if (StringUtils.hasText(request.getSubject())) {
|
||||
wrapper.like(CertificateCrlRevokedEntity::getSubjectDn, request.getSubject());
|
||||
}
|
||||
wrapper.orderByDesc(CertificateCrlRevokedEntity::getRevocationTime);
|
||||
long total = revokedMapper.selectCount(wrapper);
|
||||
long offset = (long) (request.getPageNum() - 1) * request.getPageSize();
|
||||
wrapper.last("LIMIT " + offset + "," + request.getPageSize());
|
||||
List<CertificateCrlRevokedEntity> records = revokedMapper.selectList(wrapper);
|
||||
Page<CertificateCrlRevokedEntity> page = new Page<>(request.getPageNum(), request.getPageSize(), total);
|
||||
page.setRecords(records);
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsRevoked(String issuerDn, String serialNumber) {
|
||||
return revokedMapper.selectCount(revocationWrapper(issuerDn, serialNumber)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CertificateCrlRevokedEntity> findRevocation(String issuerDn, String serialNumber) {
|
||||
return Optional.ofNullable(revokedMapper.selectOne(revocationWrapper(issuerDn, serialNumber).last("LIMIT 1")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRevokedSubjectIfMissing(String issuerDn, String serialNumber, String subjectDn, String algoType) {
|
||||
revokedMapper.update(null, new LambdaUpdateWrapper<CertificateCrlRevokedEntity>()
|
||||
.eq(CertificateCrlRevokedEntity::getIssuerDn, issuerDn)
|
||||
.eq(CertificateCrlRevokedEntity::getSerialNumber, serialNumber)
|
||||
.isNull(CertificateCrlRevokedEntity::getSubjectDn)
|
||||
.set(CertificateCrlRevokedEntity::getSubjectDn, subjectDn)
|
||||
.set(CertificateCrlRevokedEntity::getAlgoType, algoType));
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<CertificateCrlRevokedEntity> revocationWrapper(String issuerDn, String serialNumber) {
|
||||
return new LambdaQueryWrapper<CertificateCrlRevokedEntity>()
|
||||
.eq(CertificateCrlRevokedEntity::getIssuerDn, issuerDn)
|
||||
.eq(CertificateCrlRevokedEntity::getSerialNumber, serialNumber)
|
||||
.apply("EXISTS (SELECT 1 FROM t_certificate_crl c WHERE c.id = crl_id AND (c.next_update IS NULL OR c.next_update >= NOW()))");
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,12 @@ public class KeyEntityRepositoryImpl implements KeyEntityRepository {
|
||||
return Optional.ofNullable(mapper.selectOne(wrapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyEntity> findAll() {
|
||||
return mapper.selectList(new LambdaQueryWrapper<KeyEntity>()
|
||||
.orderByDesc(KeyEntity::getCreateTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> findUsedKeyIdx() {
|
||||
LambdaQueryWrapper<KeyEntity> wrapper = new LambdaQueryWrapper<KeyEntity>()
|
||||
|
||||
+12
-5
@@ -62,13 +62,21 @@ public class TrustedCertRepositoryImpl implements TrustedCertRepository {
|
||||
.eq(TrustedCertEntity::getIssuerDn, issuerDn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByIssuerDnExcludingId(String issuerDn, Long excludedId) {
|
||||
return mapper.selectCount(new LambdaQueryWrapper<TrustedCertEntity>()
|
||||
.eq(TrustedCertEntity::getIssuerDn, issuerDn)
|
||||
.ne(excludedId != null, TrustedCertEntity::getId, excludedId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TrustedCertEntity> page(TrustedCertListRequest request) {
|
||||
LambdaQueryWrapper<TrustedCertEntity> 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()));
|
||||
if (StringUtils.hasText(request.getAlgoType())) {
|
||||
wrapper.eq(TrustedCertEntity::getAlgoType, request.getAlgoType().toUpperCase());
|
||||
}
|
||||
if (StringUtils.hasText(request.getSubject())) {
|
||||
wrapper.like(TrustedCertEntity::getSubjectDn, request.getSubject());
|
||||
}
|
||||
wrapper.orderByDesc(TrustedCertEntity::getImportTime);
|
||||
long total = mapper.selectCount(wrapper);
|
||||
@@ -80,4 +88,3 @@ public class TrustedCertRepositoryImpl implements TrustedCertRepository {
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ 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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -19,7 +19,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class CertificateService {
|
||||
@@ -27,15 +27,27 @@ public class CertificateService {
|
||||
private final CertificateRepository certificateRepository;
|
||||
private final EntityService entityService;
|
||||
private final TrustedCertService trustedCertService;
|
||||
private final CrlRevocationService crlRevocationService;
|
||||
|
||||
public CertificateService(
|
||||
CertificateRepository certificateRepository,
|
||||
EntityService entityService,
|
||||
TrustedCertService trustedCertService
|
||||
) {
|
||||
this(certificateRepository, entityService, trustedCertService, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CertificateService(
|
||||
CertificateRepository certificateRepository,
|
||||
EntityService entityService,
|
||||
TrustedCertService trustedCertService,
|
||||
CrlRevocationService crlRevocationService
|
||||
) {
|
||||
this.certificateRepository = certificateRepository;
|
||||
this.entityService = entityService;
|
||||
this.trustedCertService = trustedCertService;
|
||||
this.crlRevocationService = crlRevocationService;
|
||||
}
|
||||
|
||||
public Page<CertificateItemResponse> list(CertificateListRequest request) {
|
||||
@@ -46,43 +58,36 @@ public class CertificateService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ImportCertificateResponse importCertificate(MultipartFile file, String certType, Long entityId) {
|
||||
public ImportCertificateResponse importCertificate(MultipartFile file) {
|
||||
try {
|
||||
//todo file空值判断
|
||||
|
||||
X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes());
|
||||
assertValidTime(certificate);
|
||||
assertEndEntityCertificate(certificate);
|
||||
String algoType = CertPemSupport.detectAlgoType(certificate);
|
||||
String fingerprint = CertPemSupport.sha256Fingerprint(certificate);
|
||||
if (certificateRepository.findByFingerprint(fingerprint).isPresent()) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "certificate already exists");
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "证书已存在");
|
||||
}
|
||||
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;
|
||||
Optional<Long> matchedEntityId = entityService.findByCertificatePublicKey(certificate.getPublicKey().getEncoded());
|
||||
Long finalEntityId = matchedEntityId.orElse(null);
|
||||
String certType = matchedEntityId.isPresent() ? "ENTITY" : "USER";
|
||||
var issuer = trustedCertService.resolveIssuer(certificate);
|
||||
trustedCertService.validateParentChain(issuer);
|
||||
String issuerDn = certificate.getIssuerX500Principal().getName();
|
||||
String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
if (isRevoked(issuerDn, serialNumber)) {
|
||||
fillRevokedSubjectIfMissing(issuerDn, serialNumber, certificate.getSubjectX500Principal().getName(), algoType);
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书已被CRL吊销");
|
||||
}
|
||||
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")
|
||||
.setIssuerDn(issuerDn)
|
||||
.setSerialNumber(serialNumber)
|
||||
.setAlgoType(algoType)
|
||||
.setFingerprint(fingerprint)
|
||||
.setCertData(CertPemSupport.toPem(certificate))
|
||||
.setValidFrom(LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault()))
|
||||
@@ -99,7 +104,7 @@ public class CertificateService {
|
||||
} catch (BizException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate import failed: " + ex.getMessage());
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书导入失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +122,7 @@ public class CertificateService {
|
||||
response.setCertData(entity.getCertData());
|
||||
response.setValidFrom(entity.getValidFrom());
|
||||
response.setValidTo(entity.getValidTo());
|
||||
response.setRuntimeStatus(CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo()));
|
||||
response.setRuntimeStatus(resolveRuntimeStatus(entity));
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -131,7 +136,11 @@ public class CertificateService {
|
||||
CertificateItemResponse response = new CertificateItemResponse();
|
||||
response.setId(entity.getId());
|
||||
response.setEntityId(entity.getEntityId());
|
||||
if (entity.getEntityId() != null) {
|
||||
entityService.findOrgCodeById(entity.getEntityId()).ifPresent(response::setOrgCode);
|
||||
}
|
||||
response.setCertType(entity.getCertType());
|
||||
response.setCertTypeName(certTypeName(entity.getCertType()));
|
||||
response.setSubjectDn(entity.getSubjectDn());
|
||||
response.setIssuerDn(entity.getIssuerDn());
|
||||
response.setSerialNumber(entity.getSerialNumber());
|
||||
@@ -139,13 +148,41 @@ public class CertificateService {
|
||||
response.setFingerprint(entity.getFingerprint());
|
||||
response.setValidFrom(entity.getValidFrom());
|
||||
response.setValidTo(entity.getValidTo());
|
||||
response.setRuntimeStatus(CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo()));
|
||||
response.setImportTime(entity.getImportTime());
|
||||
response.setRuntimeStatus(resolveRuntimeStatus(entity));
|
||||
return response;
|
||||
}
|
||||
|
||||
private String resolveRuntimeStatus(CertificateEntity entity) {
|
||||
if (isRevoked(entity.getIssuerDn(), entity.getSerialNumber())) {
|
||||
return "REVOKED";
|
||||
}
|
||||
return CertRuntimeStatusResolver.resolve(entity.getValidFrom(), entity.getValidTo());
|
||||
}
|
||||
|
||||
private boolean isRevoked(String issuerDn, String serialNumber) {
|
||||
return crlRevocationService != null && crlRevocationService.isRevoked(issuerDn, serialNumber);
|
||||
}
|
||||
|
||||
private void fillRevokedSubjectIfMissing(String issuerDn, String serialNumber, String subjectDn, String algoType) {
|
||||
if (crlRevocationService != null && crlRevocationService.findRevocation(issuerDn, serialNumber).isPresent()) {
|
||||
crlRevocationService.fillSubjectIfMissing(issuerDn, serialNumber, subjectDn, algoType);
|
||||
}
|
||||
}
|
||||
|
||||
private String certTypeName(String certType) {
|
||||
if ("ENTITY".equalsIgnoreCase(certType)) {
|
||||
return "实体证书";
|
||||
}
|
||||
if ("USER".equalsIgnoreCase(certType)) {
|
||||
return "用户证书";
|
||||
}
|
||||
return certType;
|
||||
}
|
||||
|
||||
private CertificateEntity getById(Long id) {
|
||||
return certificateRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate not found"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书不存在"));
|
||||
}
|
||||
|
||||
private void assertValidTime(X509Certificate certificate) {
|
||||
@@ -153,7 +190,17 @@ public class CertificateService {
|
||||
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");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书不在有效期内");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEndEntityCertificate(X509Certificate certificate) {
|
||||
if (certificate.getBasicConstraints() >= 0) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体/用户证书不能是CA证书");
|
||||
}
|
||||
boolean[] keyUsage = certificate.getKeyUsage();
|
||||
if (keyUsage != null && (keyUsage.length <= 0 || !keyUsage[0])) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体/用户证书必须允许数字签名");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.cisd.tms.modules.cert.service;
|
||||
|
||||
import com.cisd.tms.modules.cert.dto.CrlRevocationInfo;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity;
|
||||
import com.cisd.tms.modules.cert.repository.CrlRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class CrlRevocationService {
|
||||
|
||||
private final CrlRepository crlRepository;
|
||||
|
||||
public CrlRevocationService(CrlRepository crlRepository) {
|
||||
this.crlRepository = crlRepository;
|
||||
}
|
||||
|
||||
public boolean isRevoked(String issuerDn, String serialNumber) {
|
||||
return crlRepository.existsRevoked(issuerDn, serialNumber);
|
||||
}
|
||||
|
||||
public Optional<CrlRevocationInfo> findRevocation(String issuerDn, String serialNumber) {
|
||||
return crlRepository.findRevocation(issuerDn, serialNumber).map(this::toInfo);
|
||||
}
|
||||
|
||||
public void fillSubjectIfMissing(String issuerDn, String serialNumber, String subjectDn, String algoType) {
|
||||
crlRepository.updateRevokedSubjectIfMissing(issuerDn, serialNumber, subjectDn, algoType);
|
||||
}
|
||||
|
||||
private CrlRevocationInfo toInfo(CertificateCrlRevokedEntity entity) {
|
||||
CrlRevocationInfo info = new CrlRevocationInfo();
|
||||
info.setIssuerDn(entity.getIssuerDn());
|
||||
info.setSerialNumber(entity.getSerialNumber());
|
||||
info.setSubjectDn(entity.getSubjectDn());
|
||||
info.setCrlNumber(entity.getCrlNumber());
|
||||
info.setRevocationReason(entity.getRevocationReason());
|
||||
info.setRevocationTime(entity.getRevocationTime());
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
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.CrlListRequest;
|
||||
import com.cisd.tms.modules.cert.dto.CrlRevokedItemResponse;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlEntity;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateCrlRevokedEntity;
|
||||
import com.cisd.tms.modules.cert.entity.CertificateEntity;
|
||||
import com.cisd.tms.modules.cert.entity.TrustedCertEntity;
|
||||
import com.cisd.tms.modules.cert.repository.CertificateRepository;
|
||||
import com.cisd.tms.modules.cert.repository.CrlRepository;
|
||||
import com.cisd.tms.modules.cert.support.CertPemSupport;
|
||||
import org.bouncycastle.asn1.ASN1OctetString;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.x509.CRLNumber;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509CRLEntry;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class CrlService {
|
||||
|
||||
private final CrlRepository crlRepository;
|
||||
private final CertificateRepository certificateRepository;
|
||||
private final TrustedCertService trustedCertService;
|
||||
|
||||
public CrlService(
|
||||
CrlRepository crlRepository,
|
||||
CertificateRepository certificateRepository,
|
||||
TrustedCertService trustedCertService
|
||||
) {
|
||||
this.crlRepository = crlRepository;
|
||||
this.certificateRepository = certificateRepository;
|
||||
this.trustedCertService = trustedCertService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void importCrl(MultipartFile file) {
|
||||
try {
|
||||
X509CRL crl = CertPemSupport.parseCrl(file.getBytes());
|
||||
assertEffective(crl);
|
||||
String fingerprint = CertPemSupport.sha256Fingerprint(crl);
|
||||
if (crlRepository.findCrlByFingerprint(fingerprint).isPresent()) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "CRL已存在");
|
||||
}
|
||||
TrustedCertEntity issuer = trustedCertService.resolveCrlIssuer(crl);
|
||||
trustedCertService.validateParentChain(issuer);
|
||||
String issuerDn = crl.getIssuerX500Principal().getName();
|
||||
String algoType = issuer.getAlgoType();
|
||||
if (algoType == null) {
|
||||
algoType = "SM2";
|
||||
}
|
||||
String crlNumber = crlNumber(crl);
|
||||
CertificateCrlEntity entity = new CertificateCrlEntity()
|
||||
.setIssuerDn(issuerDn)
|
||||
.setCrlNumber(crlNumber)
|
||||
.setAlgoType(algoType)
|
||||
.setFingerprint(fingerprint)
|
||||
.setCrlData(CertPemSupport.toPem(crl))
|
||||
.setThisUpdate(toLocalDateTime(crl.getThisUpdate()))
|
||||
.setNextUpdate(crl.getNextUpdate() == null ? null : toLocalDateTime(crl.getNextUpdate()))
|
||||
.setRevokedCount(crl.getRevokedCertificates() == null ? 0 : crl.getRevokedCertificates().size())
|
||||
.setImportTime(LocalDateTime.now())
|
||||
.setCreateBy("system");
|
||||
entity.setCreateTime(LocalDateTime.now());
|
||||
entity.setUpdateTime(LocalDateTime.now());
|
||||
crlRepository.saveCrl(entity);
|
||||
if (crl.getRevokedCertificates() != null) {
|
||||
for (X509CRLEntry entry : crl.getRevokedCertificates()) {
|
||||
saveRevoked(entity.getId(), issuerDn, crlNumber, algoType, entry);
|
||||
}
|
||||
}
|
||||
} catch (BizException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL导入失败");
|
||||
}
|
||||
}
|
||||
|
||||
public Page<CrlRevokedItemResponse> list(CrlListRequest request) {
|
||||
Page<CertificateCrlRevokedEntity> page = crlRepository.pageRevoked(request);
|
||||
Page<CrlRevokedItemResponse> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
result.setRecords(page.getRecords().stream().map(this::toItem).toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
crlRepository.findCrlById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL不存在"));
|
||||
crlRepository.deleteRevokedByCrlId(id);
|
||||
crlRepository.deleteCrlById(id);
|
||||
}
|
||||
|
||||
private void saveRevoked(Long crlId, String issuerDn, String crlNumber, String crlAlgoType, X509CRLEntry entry) {
|
||||
String serialNumber = entry.getSerialNumber().toString(16).toUpperCase();
|
||||
Optional<CertificateEntity> certificate = certificateRepository.findByIssuerDnAndSerialNumber(issuerDn, serialNumber);
|
||||
CertificateCrlRevokedEntity revoked = new CertificateCrlRevokedEntity()
|
||||
.setCrlId(crlId)
|
||||
.setIssuerDn(issuerDn)
|
||||
.setSerialNumber(serialNumber)
|
||||
.setSubjectDn(certificate.map(CertificateEntity::getSubjectDn).orElse(null))
|
||||
.setAlgoType(certificate.map(CertificateEntity::getAlgoType).orElse(crlAlgoType))
|
||||
.setCrlNumber(crlNumber)
|
||||
.setRevocationTime(toLocalDateTime(entry.getRevocationDate()))
|
||||
.setRevocationReason(reasonName(entry));
|
||||
revoked.setCreateTime(LocalDateTime.now());
|
||||
revoked.setUpdateTime(LocalDateTime.now());
|
||||
crlRepository.saveRevoked(revoked);
|
||||
}
|
||||
|
||||
private CrlRevokedItemResponse toItem(CertificateCrlRevokedEntity entity) {
|
||||
CrlRevokedItemResponse response = new CrlRevokedItemResponse();
|
||||
response.setId(entity.getId());
|
||||
response.setSubjectDn(entity.getSubjectDn());
|
||||
response.setIssuerDn(entity.getIssuerDn());
|
||||
response.setSerialNumber(entity.getSerialNumber());
|
||||
response.setAlgoType(entity.getAlgoType());
|
||||
response.setCrlNumber(entity.getCrlNumber());
|
||||
response.setRevocationReason(entity.getRevocationReason());
|
||||
response.setRevocationTime(entity.getRevocationTime());
|
||||
response.setPublishTime(entity.getCreateTime());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void assertEffective(X509CRL crl) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime thisUpdate = toLocalDateTime(crl.getThisUpdate());
|
||||
if (thisUpdate.isAfter(now)) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL生效时间晚于当前时间");
|
||||
}
|
||||
if (crl.getNextUpdate() != null && toLocalDateTime(crl.getNextUpdate()).isBefore(now)) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL已过期");
|
||||
}
|
||||
}
|
||||
|
||||
private String reasonName(X509CRLEntry entry) {
|
||||
if (entry.getRevocationReason() == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (entry.getRevocationReason()) {
|
||||
case KEY_COMPROMISE -> "keyCompromise";
|
||||
case CA_COMPROMISE -> "cACompromise";
|
||||
case AFFILIATION_CHANGED -> "affiliationChanged";
|
||||
case SUPERSEDED -> "superseded";
|
||||
case CESSATION_OF_OPERATION -> "cessationOfOperation";
|
||||
case CERTIFICATE_HOLD -> "certificateHold";
|
||||
case REMOVE_FROM_CRL -> "removeFromCRL";
|
||||
case PRIVILEGE_WITHDRAWN -> "privilegeWithdrawn";
|
||||
case AA_COMPROMISE -> "aACompromise";
|
||||
case UNUSED -> "unused";
|
||||
default -> entry.getRevocationReason().name();
|
||||
};
|
||||
}
|
||||
|
||||
private String crlNumber(X509CRL crl) {
|
||||
try {
|
||||
byte[] extensionValue = crl.getExtensionValue(Extension.cRLNumber.getId());
|
||||
if (extensionValue == null) {
|
||||
return null;
|
||||
}
|
||||
ASN1OctetString octets = ASN1OctetString.getInstance(ASN1Primitive.fromByteArray(extensionValue));
|
||||
CRLNumber number = CRLNumber.getInstance(ASN1Primitive.fromByteArray(octets.getOctets()));
|
||||
return number.getCRLNumber().toString();
|
||||
} catch (Exception ex) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL编号扩展无效");
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDateTime toLocalDateTime(java.util.Date date) {
|
||||
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,11 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class EntityService {
|
||||
@@ -75,7 +77,7 @@ public class EntityService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EntityCreateResponse create(EntityCreateRequest request) {
|
||||
public synchronized EntityCreateResponse create(EntityCreateRequest request) {
|
||||
String algoType = normalizeAlgoType(request.getAlgoType());
|
||||
int keyIdx = resolveNextKeyIdx();
|
||||
int keyLen = SM2.equals(algoType) ? 256 : 2048;
|
||||
@@ -137,7 +139,7 @@ public class EntityService {
|
||||
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");
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "实体已绑定证书,请先删除证书");
|
||||
}
|
||||
pcieCryptoService.deleteUserKey(entity.getKeyIdx());
|
||||
keyEntityRepository.deleteById(id);
|
||||
@@ -147,7 +149,7 @@ public class EntityService {
|
||||
public String generateP10(EntityGenP10Request request) {
|
||||
KeyEntity entity = getById(request.getId());
|
||||
if (!SM2.equalsIgnoreCase(entity.getAlgoType())) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "only SM2 P10 is supported");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "仅支持SM2生成P10请求");
|
||||
}
|
||||
EntityCertMode certMode = EntityCertMode.requireValid(request.getCertMode());
|
||||
Subject subject = request.getSubject();
|
||||
@@ -173,7 +175,19 @@ public class EntityService {
|
||||
|
||||
public KeyEntity getById(Long id) {
|
||||
return keyEntityRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "entity not found"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "实体不存在"));
|
||||
}
|
||||
|
||||
public Optional<Long> findByCertificatePublicKey(byte[] certificatePublicKey) {
|
||||
return keyEntityRepository.findAll().stream()
|
||||
.filter(entity -> certificatePublicKeyMatches(entity, certificatePublicKey))
|
||||
.map(KeyEntity::getId)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<String> findOrgCodeById(Long id) {
|
||||
return keyEntityRepository.findById(id)
|
||||
.map(KeyEntity::getOrgCode);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -201,15 +215,28 @@ public class EntityService {
|
||||
|
||||
private String normalizeAlgoType(String algoType) {
|
||||
if (!StringUtils.hasText(algoType)) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "algoType is required");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "算法类型不能为空");
|
||||
}
|
||||
String normalized = algoType.toUpperCase(Locale.ROOT);
|
||||
if (!SM2.equals(normalized) && !RSA.equals(normalized)) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "algoType must be SM2 or RSA");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "算法类型必须为SM2或RSA");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private boolean certificatePublicKeyMatches(KeyEntity entity, byte[] certificatePublicKey) {
|
||||
if (entity == null || !SM2.equalsIgnoreCase(entity.getAlgoType()) || !StringUtils.hasText(entity.getPublicKey())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
byte[] entityPublicKey = Base64.getDecoder().decode(entity.getPublicKey());
|
||||
byte[] entityX509Key = Sm2KeySupport.toSm2PublicKey(entityPublicKey).getEncoded();
|
||||
return Arrays.equals(certificatePublicKey, entityX509Key);
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveNextKeyIdx() {
|
||||
List<Integer> used = keyEntityRepository.findUsedKeyIdx();
|
||||
for (int idx = 2; idx <= 8; idx++) {
|
||||
@@ -217,14 +244,14 @@ public class EntityService {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "no available key index in [2..8]");
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "无可用密钥索引[2..8]");
|
||||
}
|
||||
|
||||
private String writeAllowIps(List<String> allowIps) {
|
||||
try {
|
||||
return allowIps == null ? "[]" : objectMapper.writeValueAsString(allowIps);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "invalid allowIps");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "允许访问IP格式无效");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +272,7 @@ public class EntityService {
|
||||
.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");
|
||||
throw new BizException(ErrorCode.INTERNAL_ERROR.getCode(), "P10请求编码失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
@@ -27,6 +28,9 @@ import java.util.List;
|
||||
@Service
|
||||
public class TrustedCertService {
|
||||
|
||||
private static final int KEY_USAGE_KEY_CERT_SIGN = 5;
|
||||
private static final int KEY_USAGE_CRL_SIGN = 6;
|
||||
|
||||
static {
|
||||
if (Security.getProvider("BC") == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
@@ -56,17 +60,16 @@ public class TrustedCertService {
|
||||
try {
|
||||
X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes());
|
||||
assertValidTime(certificate);
|
||||
assertTrustedCaCertificate(certificate);
|
||||
String fingerprint = CertPemSupport.sha256Fingerprint(certificate);
|
||||
if (trustedCertRepository.findByFingerprint(fingerprint).isPresent()) {
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "trusted certificate already exists");
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "可信CA证书已存在");
|
||||
}
|
||||
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");
|
||||
TrustedCertEntity issuer = resolveIssuer(certificate);
|
||||
validateParentChain(issuer);
|
||||
}
|
||||
TrustedCertEntity entity = new TrustedCertEntity()
|
||||
@@ -74,7 +77,7 @@ public class TrustedCertService {
|
||||
.setSubjectDn(certificate.getSubjectX500Principal().getName())
|
||||
.setIssuerDn(certificate.getIssuerX500Principal().getName())
|
||||
.setSerialNumber(certificate.getSerialNumber().toString(16).toUpperCase())
|
||||
.setAlgoType("SM2")
|
||||
.setAlgoType(CertPemSupport.detectAlgoType(certificate))
|
||||
.setFingerprint(fingerprint)
|
||||
.setCertData(CertPemSupport.toPem(certificate))
|
||||
.setCaLevel(caLevel)
|
||||
@@ -89,7 +92,7 @@ public class TrustedCertService {
|
||||
} catch (BizException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "trusted certificate import failed: " + ex.getMessage());
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "可信CA证书导入失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +117,10 @@ public class TrustedCertService {
|
||||
@Transactional
|
||||
public DeleteDependencyResponse delete(TrustedCertDeleteRequest request) {
|
||||
TrustedCertEntity entity = getById(request.getId());
|
||||
long childCaCount = trustedCertRepository.countByIssuerDn(entity.getSubjectDn());
|
||||
long childCaCount = trustedCertRepository.countByIssuerDnExcludingId(entity.getSubjectDn(), entity.getId());
|
||||
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");
|
||||
throw new BizException(ErrorCode.CONFLICT.getCode(), "可信CA证书存在下级证书,请先删除依赖证书或强制删除");
|
||||
}
|
||||
trustedCertRepository.deleteById(request.getId());
|
||||
return new DeleteDependencyResponse(childCaCount, issuedCertCount);
|
||||
@@ -126,25 +129,64 @@ public class TrustedCertService {
|
||||
public TrustedCertEntity resolveIssuer(String subjectDn) {
|
||||
List<TrustedCertEntity> matched = trustedCertRepository.findBySubjectDn(subjectDn);
|
||||
if (matched.isEmpty()) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "issuer certificate not found");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "签发者证书不存在");
|
||||
}
|
||||
return matched.get(0);
|
||||
}
|
||||
|
||||
public void validateParentChain(TrustedCertEntity entity) {
|
||||
if (entity.getSubjectDn().equals(entity.getIssuerDn())) {
|
||||
return;
|
||||
public TrustedCertEntity resolveIssuer(X509Certificate certificate) {
|
||||
List<TrustedCertEntity> matched = trustedCertRepository.findBySubjectDn(
|
||||
certificate.getIssuerX500Principal().getName()
|
||||
);
|
||||
if (matched.isEmpty()) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "签发者证书不存在");
|
||||
}
|
||||
for (TrustedCertEntity candidate : matched) {
|
||||
try {
|
||||
X509Certificate issuerCert = CertPemSupport.parseCertificate(candidate.getCertData().getBytes());
|
||||
assertTrustedCaCertificate(issuerCert);
|
||||
certificate.verify(issuerCert.getPublicKey(), "BC");
|
||||
return candidate;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "未找到有效签发者证书或证书签名无效");
|
||||
}
|
||||
|
||||
public TrustedCertEntity resolveCrlIssuer(X509CRL crl) {
|
||||
List<TrustedCertEntity> matched = trustedCertRepository.findBySubjectDn(
|
||||
crl.getIssuerX500Principal().getName()
|
||||
);
|
||||
if (matched.isEmpty()) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL签发者证书不存在");
|
||||
}
|
||||
for (TrustedCertEntity candidate : matched) {
|
||||
try {
|
||||
X509Certificate issuerCert = CertPemSupport.parseCertificate(candidate.getCertData().getBytes());
|
||||
assertCrlIssuerCertificate(issuerCert);
|
||||
crl.verify(issuerCert.getPublicKey(), "BC");
|
||||
return candidate;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "未找到有效CRL签发者证书或CRL签名无效");
|
||||
}
|
||||
|
||||
public void validateParentChain(TrustedCertEntity entity) {
|
||||
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");
|
||||
assertValidTime(certificate);
|
||||
assertTrustedCaCertificate(certificate);
|
||||
if (entity.getSubjectDn().equals(entity.getIssuerDn())) {
|
||||
certificate.verify(certificate.getPublicKey(), "BC");
|
||||
return;
|
||||
}
|
||||
TrustedCertEntity issuer = resolveIssuer(certificate);
|
||||
validateParentChain(issuer);
|
||||
} catch (BizException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "certificate chain validation failed");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书链校验失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +208,7 @@ public class TrustedCertService {
|
||||
|
||||
private TrustedCertEntity getById(Long id) {
|
||||
return trustedCertRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "trusted certificate not found"));
|
||||
.orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "可信CA证书不存在"));
|
||||
}
|
||||
|
||||
private void assertValidTime(X509Certificate certificate) {
|
||||
@@ -174,7 +216,27 @@ public class TrustedCertService {
|
||||
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");
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书不在有效期内");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTrustedCaCertificate(X509Certificate certificate) {
|
||||
if (certificate.getBasicConstraints() < 0) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "可信证书必须是CA证书");
|
||||
}
|
||||
boolean[] keyUsage = certificate.getKeyUsage();
|
||||
if (keyUsage == null || keyUsage.length <= KEY_USAGE_KEY_CERT_SIGN || !keyUsage[KEY_USAGE_KEY_CERT_SIGN]) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "可信CA证书必须允许签发证书");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertCrlIssuerCertificate(X509Certificate certificate) {
|
||||
if (certificate.getBasicConstraints() < 0) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL签发者必须是CA证书");
|
||||
}
|
||||
boolean[] keyUsage = certificate.getKeyUsage();
|
||||
if (keyUsage == null || keyUsage.length <= KEY_USAGE_CRL_SIGN || !keyUsage[KEY_USAGE_CRL_SIGN]) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "CRL签发者必须允许签发CRL");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class CertPemSupport {
|
||||
|
||||
@@ -25,11 +28,21 @@ public final class CertPemSupport {
|
||||
|
||||
public static X509Certificate parseCertificate(byte[] bytes) {
|
||||
try {
|
||||
byte[] normalized = toDer(bytes);
|
||||
byte[] normalized = toDer(bytes, "CERTIFICATE");
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509", PROVIDER);
|
||||
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(normalized));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("invalid certificate file", e);
|
||||
throw new IllegalArgumentException("证书文件无效", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static X509CRL parseCrl(byte[] bytes) {
|
||||
try {
|
||||
byte[] normalized = toDer(bytes, "X509 CRL", "CRL");
|
||||
CertificateFactory factory = CertificateFactory.getInstance("X.509", PROVIDER);
|
||||
return (X509CRL) factory.generateCRL(new ByteArrayInputStream(normalized));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("CRL文件无效", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +52,17 @@ public final class CertPemSupport {
|
||||
.encodeToString(certificate.getEncoded());
|
||||
return "-----BEGIN CERTIFICATE-----\n" + base64 + "\n-----END CERTIFICATE-----";
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("failed to encode certificate pem", e);
|
||||
throw new IllegalArgumentException("证书PEM编码失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toPem(X509CRL crl) {
|
||||
try {
|
||||
String base64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8))
|
||||
.encodeToString(crl.getEncoded());
|
||||
return "-----BEGIN X509 CRL-----\n" + base64 + "\n-----END X509 CRL-----";
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("CRL PEM编码失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,20 +71,48 @@ public final class CertPemSupport {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return hex(digest.digest(certificate.getEncoded()));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("failed to calculate fingerprint", e);
|
||||
throw new IllegalArgumentException("证书指纹计算失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] toDer(byte[] bytes) {
|
||||
String text = new String(bytes, StandardCharsets.UTF_8);
|
||||
if (!text.contains("BEGIN CERTIFICATE")) {
|
||||
return bytes;
|
||||
public static String sha256Fingerprint(X509CRL crl) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return hex(digest.digest(crl.getEncoded()));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("CRL指纹计算失败", e);
|
||||
}
|
||||
String normalized = text
|
||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
||||
.replace("-----END CERTIFICATE-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
return Base64.getDecoder().decode(normalized);
|
||||
}
|
||||
|
||||
public static String detectAlgoType(X509Certificate certificate) {
|
||||
PublicKey publicKey = certificate.getPublicKey();
|
||||
if ("RSA".equalsIgnoreCase(publicKey.getAlgorithm())) {
|
||||
return "RSA";
|
||||
}
|
||||
String signatureName = certificate.getSigAlgName();
|
||||
String signatureOid = certificate.getSigAlgOID();
|
||||
if ((signatureName != null && signatureName.toUpperCase(Locale.ROOT).contains("SM2"))
|
||||
|| (signatureOid != null && signatureOid.startsWith("1.2.156.10197.1."))) {
|
||||
return "SM2";
|
||||
}
|
||||
if ("EC".equalsIgnoreCase(publicKey.getAlgorithm())) {
|
||||
return "SM2";
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的证书算法:" + publicKey.getAlgorithm());
|
||||
}
|
||||
|
||||
private static byte[] toDer(byte[] bytes, String... labels) {
|
||||
String text = new String(bytes, StandardCharsets.UTF_8);
|
||||
for (String label : labels) {
|
||||
if (text.contains("BEGIN " + label)) {
|
||||
String normalized = text
|
||||
.replace("-----BEGIN " + label + "-----", "")
|
||||
.replace("-----END " + label + "-----", "")
|
||||
.replaceAll("\\s+", "");
|
||||
return Base64.getDecoder().decode(normalized);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static String hex(byte[] bytes) {
|
||||
@@ -72,4 +123,3 @@ public final class CertPemSupport {
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class Sm2KeySupport {
|
||||
);
|
||||
return KeyFactory.getInstance("EC", PROVIDER).generatePublic(publicKeySpec);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("failed to parse sm2 public key", e);
|
||||
throw new IllegalArgumentException("SM2公钥解析失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class Sm2KeySupport {
|
||||
try {
|
||||
return new DERSequence(vector).getEncoded();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("failed to encode sm2 signature", e);
|
||||
throw new IllegalArgumentException("SM2签名编码失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +81,3 @@ public final class Sm2KeySupport {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ tms:
|
||||
# 是否启用 /api/** 的内部登录鉴权;开发联调时可临时关闭。
|
||||
enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:false}
|
||||
# 关闭内部鉴权后,注入请求上下文的调试角色。
|
||||
debug-role-code: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_ROLE_CODE:OPS_ADMIN}
|
||||
debug-role-code: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_ROLE_CODE:KEY_ADMIN}
|
||||
# 关闭内部鉴权后,注入请求上下文的调试认证等级。
|
||||
debug-auth-level: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_AUTH_LEVEL:FULL}
|
||||
# 关闭内部鉴权后,注入请求上下文的调试 session token。
|
||||
|
||||
@@ -607,3 +607,41 @@ CREATE TABLE IF NOT EXISTS t_trusted_cert (
|
||||
KEY idx_t_trusted_cert_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_t_trusted_cert_valid_to (valid_to)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS t_certificate_crl (
|
||||
id BIGINT PRIMARY KEY,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
crl_number VARCHAR(128) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint',
|
||||
crl_data MEDIUMTEXT NOT NULL COMMENT 'CRL PEM content',
|
||||
this_update DATETIME(3) NOT NULL,
|
||||
next_update DATETIME(3) NULL,
|
||||
revoked_count INT NOT NULL,
|
||||
import_time DATETIME(3) NOT NULL,
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_t_certificate_crl_fingerprint (fingerprint),
|
||||
KEY idx_t_certificate_crl_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_t_certificate_crl_this_update (this_update),
|
||||
KEY idx_t_certificate_crl_next_update (next_update)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS t_certificate_crl_revoked (
|
||||
id BIGINT PRIMARY KEY,
|
||||
crl_id BIGINT NOT NULL,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
serial_number VARCHAR(128) NOT NULL,
|
||||
subject_dn VARCHAR(1024) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
crl_number VARCHAR(128) NULL,
|
||||
revocation_time DATETIME(3) NOT NULL,
|
||||
revocation_reason VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
KEY idx_t_certificate_crl_revoked_crl_id (crl_id),
|
||||
KEY idx_t_certificate_crl_revoked_issuer_serial (issuer_dn(512), serial_number),
|
||||
KEY idx_t_certificate_crl_revoked_subject_dn (subject_dn(512)),
|
||||
KEY idx_t_certificate_crl_revoked_revocation_time (revocation_time)
|
||||
);
|
||||
|
||||
@@ -74,3 +74,41 @@ CREATE TABLE IF NOT EXISTS t_trusted_cert (
|
||||
KEY idx_t_trusted_cert_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_t_trusted_cert_valid_to (valid_to)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS t_certificate_crl (
|
||||
id BIGINT PRIMARY KEY,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
crl_number VARCHAR(128) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
fingerprint VARCHAR(128) NOT NULL COMMENT 'SHA-256 fingerprint',
|
||||
crl_data MEDIUMTEXT NOT NULL COMMENT 'CRL PEM content',
|
||||
this_update DATETIME(3) NOT NULL,
|
||||
next_update DATETIME(3) NULL,
|
||||
revoked_count INT NOT NULL,
|
||||
import_time DATETIME(3) NOT NULL,
|
||||
create_by VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
UNIQUE KEY uk_t_certificate_crl_fingerprint (fingerprint),
|
||||
KEY idx_t_certificate_crl_issuer_dn (issuer_dn(512)),
|
||||
KEY idx_t_certificate_crl_this_update (this_update),
|
||||
KEY idx_t_certificate_crl_next_update (next_update)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS t_certificate_crl_revoked (
|
||||
id BIGINT PRIMARY KEY,
|
||||
crl_id BIGINT NOT NULL,
|
||||
issuer_dn VARCHAR(1024) NOT NULL,
|
||||
serial_number VARCHAR(128) NOT NULL,
|
||||
subject_dn VARCHAR(1024) NULL,
|
||||
algo_type VARCHAR(16) NOT NULL COMMENT 'SM2 in V1',
|
||||
crl_number VARCHAR(128) NULL,
|
||||
revocation_time DATETIME(3) NOT NULL,
|
||||
revocation_reason VARCHAR(64) NULL,
|
||||
create_time DATETIME(3) NOT NULL,
|
||||
update_time DATETIME(3) NOT NULL,
|
||||
KEY idx_t_certificate_crl_revoked_crl_id (crl_id),
|
||||
KEY idx_t_certificate_crl_revoked_issuer_serial (issuer_dn(512), serial_number),
|
||||
KEY idx_t_certificate_crl_revoked_subject_dn (subject_dn(512)),
|
||||
KEY idx_t_certificate_crl_revoked_revocation_time (revocation_time)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user