feat:根证书导入增加指纹白名单校验
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package com.cisd.tms.modules.cert.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "tms.cert")
|
||||
public class CertProperties {
|
||||
|
||||
/**
|
||||
* 允许导入为信任锚的根 CA 证书 SHA-256 指纹。为空时保持兼容,不限制根 CA 导入。
|
||||
*/
|
||||
private List<String> trustedRootFingerprints = new ArrayList<>();
|
||||
|
||||
public List<String> getTrustedRootFingerprints() {
|
||||
return trustedRootFingerprints;
|
||||
}
|
||||
|
||||
public void setTrustedRootFingerprints(List<String> trustedRootFingerprints) {
|
||||
this.trustedRootFingerprints = trustedRootFingerprints == null ? new ArrayList<>() : trustedRootFingerprints;
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,16 @@ 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.config.CertProperties;
|
||||
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.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -24,7 +27,10 @@ import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class TrustedCertService {
|
||||
@@ -40,13 +46,32 @@ public class TrustedCertService {
|
||||
|
||||
private final TrustedCertRepository trustedCertRepository;
|
||||
private final CertificateRepository certificateRepository;
|
||||
private final Set<String> trustedRootFingerprints;
|
||||
|
||||
public TrustedCertService(
|
||||
TrustedCertRepository trustedCertRepository,
|
||||
CertificateRepository certificateRepository
|
||||
) {
|
||||
this(trustedCertRepository, certificateRepository, List.of());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public TrustedCertService(
|
||||
TrustedCertRepository trustedCertRepository,
|
||||
CertificateRepository certificateRepository,
|
||||
CertProperties certProperties
|
||||
) {
|
||||
this(trustedCertRepository, certificateRepository, certProperties.getTrustedRootFingerprints());
|
||||
}
|
||||
|
||||
TrustedCertService(
|
||||
TrustedCertRepository trustedCertRepository,
|
||||
CertificateRepository certificateRepository,
|
||||
List<String> trustedRootFingerprints
|
||||
) {
|
||||
this.trustedCertRepository = trustedCertRepository;
|
||||
this.certificateRepository = certificateRepository;
|
||||
this.trustedRootFingerprints = normalizeFingerprints(trustedRootFingerprints);
|
||||
}
|
||||
|
||||
public Page<TrustedCertItemResponse> list(TrustedCertListRequest request) {
|
||||
@@ -69,6 +94,7 @@ public class TrustedCertService {
|
||||
}
|
||||
int caLevel = isSelfSigned(certificate) ? 0 : 1;
|
||||
if (caLevel == 0) {
|
||||
assertRootFingerprintAllowed(fingerprint);
|
||||
// 根 CA 必须能自签自验,防止把 subject=issuer 的伪造证书放入信任锚。
|
||||
certificate.verify(certificate.getPublicKey(), "BC");
|
||||
} else {
|
||||
@@ -269,4 +295,30 @@ public class TrustedCertService {
|
||||
private boolean isSelfSigned(X509Certificate certificate) {
|
||||
return certificate.getSubjectX500Principal().equals(certificate.getIssuerX500Principal());
|
||||
}
|
||||
|
||||
private void assertRootFingerprintAllowed(String fingerprint) {
|
||||
if (trustedRootFingerprints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!trustedRootFingerprints.contains(normalizeFingerprint(fingerprint))) {
|
||||
throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "根CA证书指纹不在白名单中");
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> normalizeFingerprints(List<String> fingerprints) {
|
||||
if (fingerprints == null) {
|
||||
return Set.of();
|
||||
}
|
||||
return fingerprints.stream()
|
||||
.map(this::normalizeFingerprint)
|
||||
.filter(StringUtils::hasText)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
private String normalizeFingerprint(String fingerprint) {
|
||||
if (!StringUtils.hasText(fingerprint)) {
|
||||
return "";
|
||||
}
|
||||
return fingerprint.replaceAll("[^0-9A-Fa-f]", "").toUpperCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ tms:
|
||||
# 上传文件大小上限(字节)。
|
||||
max-file-size-bytes: ${TMS_FILE_STORAGE_MAX_FILE_SIZE_BYTES:104857600}
|
||||
cert:
|
||||
# 允许导入为根 CA 信任锚的 SHA-256 指纹白名单,多个值用英文逗号分隔;为空时不限制根 CA 导入。
|
||||
trusted-root-fingerprints: ${TMS_CERT_TRUSTED_ROOT_FINGERPRINTS:}
|
||||
crl:
|
||||
# CRL 导入临时目录;原始文件只在解析过程中暂存,导入结束后删除。
|
||||
temp-dir: ${TMS_CERT_CRL_TEMP_DIR:/home/tms/tmp/crl-import}
|
||||
|
||||
Reference in New Issue
Block a user