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