Merge branch 'master' of http://118.31.7.2:3000/chenyuan1/digital-rmb-backend
# Conflicts: # src/main/java/com/yau/digitalrmb/security/application/JwtTokenService.java # src/main/resources/schema.sqlmaster
commit
e26c9a14bb
@ -0,0 +1,44 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionApplicationPackageResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String bankCode;
|
||||
private final String timestamp;
|
||||
private final String digest;
|
||||
private final String signature;
|
||||
|
||||
public InstitutionApplicationPackageResult(long applicationId, String status, String bankCode, String timestamp,
|
||||
String digest, String signature) {
|
||||
this.applicationId = applicationId;
|
||||
this.status = status;
|
||||
this.bankCode = bankCode;
|
||||
this.timestamp = timestamp;
|
||||
this.digest = digest;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getBankCode() {
|
||||
return bankCode;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getDigest() {
|
||||
return digest;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionApplicationVerificationResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String verificationAlgorithm;
|
||||
private final String digestAlgorithm;
|
||||
private final String signatureHashAlgorithm;
|
||||
private final String verificationKeyId;
|
||||
private final String originalText;
|
||||
private final String submittedDigest;
|
||||
private final String recalculatedDigest;
|
||||
private final boolean signatureValid;
|
||||
private final boolean digestMatches;
|
||||
private final boolean verified;
|
||||
private final String signatureConclusion;
|
||||
private final String integrityConclusion;
|
||||
|
||||
public InstitutionApplicationVerificationResult(long applicationId, String status, String verificationAlgorithm,
|
||||
String verificationKeyId, String originalText, String submittedDigest, String recalculatedDigest,
|
||||
boolean signatureValid, boolean digestMatches) {
|
||||
this.applicationId = applicationId; this.status = status;
|
||||
this.verificationAlgorithm = verificationAlgorithm; this.digestAlgorithm = "SM3";
|
||||
this.signatureHashAlgorithm = "SM3"; this.verificationKeyId = verificationKeyId; this.originalText = originalText;
|
||||
this.submittedDigest = submittedDigest; this.recalculatedDigest = recalculatedDigest; this.signatureValid = signatureValid;
|
||||
this.digestMatches = digestMatches; this.verified = signatureValid && digestMatches;
|
||||
this.signatureConclusion = signatureValid ? "公钥与商业银行第二私钥匹配,身份真实性验证通过" : "签名验证失败,无法确认申请方身份";
|
||||
this.integrityConclusion = digestMatches ? "重算摘要与申请摘要一致,数据完整性验证通过" : "重算摘要与申请摘要不一致,申请数据可能已被篡改";
|
||||
}
|
||||
public long getApplicationId() { return applicationId; }
|
||||
public String getStatus() { return status; }
|
||||
public String getVerificationAlgorithm() { return verificationAlgorithm; }
|
||||
public String getDigestAlgorithm() { return digestAlgorithm; }
|
||||
public String getSignatureHashAlgorithm() { return signatureHashAlgorithm; }
|
||||
public String getVerificationKeyId() { return verificationKeyId; }
|
||||
public String getOriginalText() { return originalText; } public String getSubmittedDigest() { return submittedDigest; }
|
||||
public String getRecalculatedDigest() { return recalculatedDigest; } public boolean isSignatureValid() { return signatureValid; }
|
||||
public boolean isDigestMatches() { return digestMatches; } public boolean isVerified() { return verified; }
|
||||
public String getSignatureConclusion() { return signatureConclusion; } public String getIntegrityConclusion() { return integrityConclusion; }
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierRepository;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Service
|
||||
public class InstitutionApplicationVerificationService {
|
||||
@Resource
|
||||
private InstitutionIdentityCryptography cryptography;
|
||||
|
||||
@Resource
|
||||
private InstitutionKeyService keyService;
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentifierRepository repository;
|
||||
|
||||
@Resource
|
||||
private InstitutionTrainingErrorRecorder errorRecorder;
|
||||
|
||||
@Transactional
|
||||
public InstitutionApplicationVerificationResult verify(long applicationId, String verificationKeyId,
|
||||
InstitutionKeySubject subject, String operator) {
|
||||
InstitutionIdentifierApplication application = requireApplication(applicationId);
|
||||
if (!InstitutionKeyService.BANK_SECOND_KEY.equals(verificationKeyId)) {
|
||||
recordAndThrow(application, "WRONG_KEY", "VERIFY", verificationKeyId,
|
||||
"验证必须选择商业银行第二密钥的公钥", operator);
|
||||
}
|
||||
requireStatus(application, InstitutionIdentifierApplication.Status.PACKAGED,
|
||||
"当前步骤不是央行验证,不能操作", operator);
|
||||
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||
String originalText = application.getBankCode() + "|" + application.getTimestamp();
|
||||
String recalculatedDigest = cryptography.sm3(originalText);
|
||||
boolean signatureValid = keyService.verifyCommercialBank(subject, application.getDigest(),
|
||||
application.getBankSignature());
|
||||
boolean digestMatches = recalculatedDigest.equalsIgnoreCase(application.getDigest());
|
||||
invokeDomain(() -> application.recordVerification(verificationKeyId, signatureValid, digestMatches));
|
||||
repository.update(application, expectedStatus, "VERIFY",
|
||||
signatureValid && digestMatches ? "SM2身份验证和SM3完整性验证通过" : "申请验证失败",
|
||||
operator);
|
||||
return new InstitutionApplicationVerificationResult(applicationId, application.getStatus().name(),
|
||||
"SM2_WITH_SM3_VERIFY_AND_SM3_RECALCULATE", verificationKeyId, originalText,
|
||||
application.getDigest(), recalculatedDigest, signatureValid, digestMatches);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InstitutionIdentifierConfirmationResult confirm(long applicationId, String signingKeyId,
|
||||
InstitutionKeySubject subject, String operator) {
|
||||
InstitutionIdentifierApplication application = requireApplication(applicationId);
|
||||
if (!InstitutionKeyService.CENTRAL_FIRST_KEY.equals(signingKeyId)) {
|
||||
recordAndThrow(application, "WRONG_KEY", "CONFIRM", signingKeyId,
|
||||
"签名确权必须选择中央银行第一密钥", operator);
|
||||
}
|
||||
requireStatus(application, InstitutionIdentifierApplication.Status.VERIFIED,
|
||||
"申请尚未验证通过,不能进行央行签名确权", operator);
|
||||
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||
String digest = application.getDigest();
|
||||
String identifier = "ORG_" + digest.substring(0, 12);
|
||||
String centralBankSignature = keyService.signCentralBank(subject, digest);
|
||||
invokeDomain(() -> application.issue(signingKeyId, identifier, centralBankSignature));
|
||||
repository.update(application, expectedStatus, "CONFIRM",
|
||||
"中央银行第一私钥完成SM2签名确权并签发机构标识", operator);
|
||||
return new InstitutionIdentifierConfirmationResult(applicationId, application.getStatus().name(),
|
||||
signingKeyId, centralBankSignature, identifier, digest);
|
||||
}
|
||||
|
||||
private InstitutionIdentifierApplication requireApplication(long applicationId) {
|
||||
return repository.findById(applicationId).orElseThrow(() ->
|
||||
new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "机构标识申请不存在"));
|
||||
}
|
||||
|
||||
private void requireStatus(InstitutionIdentifierApplication application,
|
||||
InstitutionIdentifierApplication.Status expectedStatus,
|
||||
String message, String operator) {
|
||||
if (application.getStatus() != expectedStatus) {
|
||||
recordAndThrow(application, "INVALID_STEP", expectedStatus.name(), null,
|
||||
message + ",当前状态:" + application.getStatus().name(), operator);
|
||||
}
|
||||
}
|
||||
|
||||
private void recordAndThrow(InstitutionIdentifierApplication application, String errorType, String operationStep,
|
||||
String providedKeyId, String message, String operator) {
|
||||
int errorSequence = errorRecorder.record(application.getId(), errorType, operationStep, providedKeyId,
|
||||
message, operator);
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR,
|
||||
message + ",本次实训第" + errorSequence + "次错误");
|
||||
}
|
||||
|
||||
private static void invokeDomain(Runnable operation) {
|
||||
try {
|
||||
operation.run();
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR, exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionDigestResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String algorithm;
|
||||
private final String concatenatedText;
|
||||
private final String digest;
|
||||
|
||||
public InstitutionDigestResult(long applicationId, String status, String algorithm, String concatenatedText,
|
||||
String digest) {
|
||||
this.applicationId = applicationId;
|
||||
this.status = status;
|
||||
this.algorithm = algorithm;
|
||||
this.concatenatedText = concatenatedText;
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
public long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public String getConcatenatedText() {
|
||||
return concatenatedText;
|
||||
}
|
||||
|
||||
public String getDigest() {
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionIdentifierConfirmationResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String signingAlgorithm;
|
||||
private final String signatureHashAlgorithm;
|
||||
private final String signatureAlgorithm;
|
||||
private final String signingKeyId;
|
||||
private final String centralBankSignature;
|
||||
private final String institutionIdentifier;
|
||||
private final String fullDigest;
|
||||
public InstitutionIdentifierConfirmationResult(long applicationId, String status, String signingKeyId,
|
||||
String centralBankSignature, String institutionIdentifier, String fullDigest) {
|
||||
this.applicationId = applicationId; this.status = status;
|
||||
this.signingAlgorithm = "SM2"; this.signatureHashAlgorithm = "SM3";
|
||||
this.signatureAlgorithm = "SM2_WITH_SM3";
|
||||
this.signingKeyId = signingKeyId; this.centralBankSignature = centralBankSignature;
|
||||
this.institutionIdentifier = institutionIdentifier; this.fullDigest = fullDigest;
|
||||
}
|
||||
public long getApplicationId() { return applicationId; }
|
||||
public String getStatus() { return status; }
|
||||
public String getSigningAlgorithm() { return signingAlgorithm; }
|
||||
public String getSignatureHashAlgorithm() { return signatureHashAlgorithm; }
|
||||
public String getSignatureAlgorithm() { return signatureAlgorithm; }
|
||||
public String getSigningKeyId() { return signingKeyId; }
|
||||
public String getCentralBankSignature() { return centralBankSignature; } public String getInstitutionIdentifier() { return institutionIdentifier; }
|
||||
public String getFullDigest() { return fullDigest; }
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierRepository;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Service
|
||||
public class InstitutionIdentifierStepService {
|
||||
private static final DateTimeFormatter TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentityCryptography cryptography;
|
||||
|
||||
@Resource
|
||||
private InstitutionKeyService keyService;
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentifierRepository repository;
|
||||
|
||||
@Resource
|
||||
private InstitutionTrainingErrorRecorder errorRecorder;
|
||||
|
||||
private final Clock clock = Clock.systemUTC();
|
||||
|
||||
@Transactional
|
||||
public InstitutionInformationPreparationResult prepare(String bankCode, String operator) {
|
||||
String normalized = normalize(bankCode);
|
||||
int trainingRound = repository.nextTrainingRound(normalized, operator);
|
||||
String timestamp = LocalDateTime.ofInstant(clock.instant(), ZoneOffset.UTC).format(TIMESTAMP);
|
||||
InstitutionIdentifierApplication application = InstitutionIdentifierApplication.prepare(normalized, timestamp);
|
||||
long applicationId = repository.save(application, trainingRound, operator);
|
||||
return new InstitutionInformationPreparationResult(applicationId, application.getStatus().name(), normalized,
|
||||
timestamp, normalized + "|" + timestamp, trainingRound, 0);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InstitutionDigestResult digest(long applicationId, String operator) {
|
||||
InstitutionIdentifierApplication application = requireApplication(applicationId);
|
||||
requireStatus(application, InstitutionIdentifierApplication.Status.PREPARED, "当前步骤不是摘要计算,不能操作", operator);
|
||||
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||
String text = application.getBankCode() + "|" + application.getTimestamp();
|
||||
String digest = cryptography.sm3(text);
|
||||
invokeDomain(() -> application.recordDigest(digest));
|
||||
repository.update(application, expectedStatus, "DIGEST", "使用SM3计算并保存业务摘要", operator);
|
||||
return new InstitutionDigestResult(applicationId, application.getStatus().name(), "SM3", text, digest);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InstitutionSignatureResult sign(long applicationId, String keyId, InstitutionKeySubject subject,
|
||||
String operator) {
|
||||
InstitutionIdentifierApplication application = requireApplication(applicationId);
|
||||
if (!InstitutionKeyService.BANK_SECOND_KEY.equals(keyId)) {
|
||||
recordAndThrow(application, "WRONG_KEY", "SIGN", keyId,
|
||||
"签名必须选择商业银行第二密钥", operator);
|
||||
}
|
||||
requireStatus(application, InstitutionIdentifierApplication.Status.DIGESTED,
|
||||
"当前步骤不是商业银行签名,不能操作", operator);
|
||||
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||
String signature = keyService.signCommercialBank(subject, application.getDigest());
|
||||
invokeDomain(() -> application.recordBankSignature(signature));
|
||||
repository.update(application, expectedStatus, "SIGN", "使用商业银行第二私钥完成SM2签名", operator);
|
||||
return new InstitutionSignatureResult(applicationId, application.getStatus().name(), "SM2", keyId,
|
||||
"COMMERCIAL_BANK_SECOND_PRIVATE_KEY", signature);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InstitutionApplicationPackageResult packageApplication(long applicationId, String operator) {
|
||||
InstitutionIdentifierApplication application = requireApplication(applicationId);
|
||||
requireStatus(application, InstitutionIdentifierApplication.Status.SIGNED, "当前步骤不是申请组装,不能操作", operator);
|
||||
InstitutionIdentifierApplication.Status expectedStatus = application.getStatus();
|
||||
invokeDomain(application::packageApplication);
|
||||
repository.update(application, expectedStatus, "PACKAGE", "组装机构标识申请材料并提交央行待验证", operator);
|
||||
return new InstitutionApplicationPackageResult(applicationId, application.getStatus().name(),
|
||||
application.getBankCode(), application.getTimestamp(), application.getDigest(),
|
||||
application.getBankSignature());
|
||||
}
|
||||
|
||||
private InstitutionIdentifierApplication requireApplication(long applicationId) {
|
||||
return repository.findById(applicationId).orElseThrow(() ->
|
||||
new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "机构标识申请不存在"));
|
||||
}
|
||||
|
||||
private void requireStatus(InstitutionIdentifierApplication application,
|
||||
InstitutionIdentifierApplication.Status expectedStatus,
|
||||
String message, String operator) {
|
||||
if (application.getStatus() != expectedStatus) {
|
||||
recordAndThrow(application, "INVALID_STEP", expectedStatus.name(), null,
|
||||
message + ",当前状态:" + application.getStatus().name(), operator);
|
||||
}
|
||||
}
|
||||
|
||||
private void recordAndThrow(InstitutionIdentifierApplication application, String errorType, String operationStep,
|
||||
String providedKeyId, String message, String operator) {
|
||||
int errorSequence = errorRecorder.record(application.getId(), errorType, operationStep, providedKeyId,
|
||||
message, operator);
|
||||
invalid(message + ",本次实训第" + errorSequence + "次错误");
|
||||
}
|
||||
|
||||
private static void invokeDomain(Runnable operation) {
|
||||
try {
|
||||
operation.run();
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
invalid(exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
return value.trim().toUpperCase();
|
||||
}
|
||||
|
||||
private static void invalid(String message) {
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR, message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionInformationPreparationResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String bankCode;
|
||||
private final String timestamp;
|
||||
private final String concatenatedText;
|
||||
private final int trainingRound;
|
||||
private final int scoringCriteria;
|
||||
|
||||
public InstitutionInformationPreparationResult(long applicationId, String status, String bankCode, String timestamp,
|
||||
String concatenatedText, int trainingRound, int scoringCriteria) {
|
||||
this.applicationId = applicationId;
|
||||
this.status = status;
|
||||
this.bankCode = bankCode;
|
||||
this.timestamp = timestamp;
|
||||
this.concatenatedText = concatenatedText;
|
||||
this.trainingRound = trainingRound;
|
||||
this.scoringCriteria = scoringCriteria;
|
||||
}
|
||||
|
||||
public long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getBankCode() {
|
||||
return bankCode;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getConcatenatedText() {
|
||||
return concatenatedText;
|
||||
}
|
||||
|
||||
public int getTrainingRound() {
|
||||
return trainingRound;
|
||||
}
|
||||
|
||||
public int getScoringCriteria() {
|
||||
return scoringCriteria;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionKeyPairResult {
|
||||
private final long recordId;
|
||||
private final String keyId;
|
||||
private final String name;
|
||||
private final String owner;
|
||||
private final String purpose;
|
||||
private final long userId;
|
||||
private final long schoolId;
|
||||
private final long classId;
|
||||
private final String algorithm;
|
||||
private final String signatureHashAlgorithm;
|
||||
private final String signatureAlgorithm;
|
||||
private final String curve;
|
||||
private final String publicKeyFormat;
|
||||
private final String privateKeyFormat;
|
||||
private final String publicKey;
|
||||
private final String privateKey;
|
||||
|
||||
public InstitutionKeyPairResult(long recordId, String keyId, String name, String owner, String purpose,
|
||||
InstitutionKeySubject subject, String publicKey, String privateKey) {
|
||||
this.recordId = recordId;
|
||||
this.keyId = keyId;
|
||||
this.name = name;
|
||||
this.owner = owner;
|
||||
this.purpose = purpose;
|
||||
this.userId = subject.getUserId();
|
||||
this.schoolId = subject.getSchoolId();
|
||||
this.classId = subject.getClassId();
|
||||
this.algorithm = "SM2";
|
||||
this.signatureHashAlgorithm = "SM3";
|
||||
this.signatureAlgorithm = "SM2_WITH_SM3";
|
||||
this.curve = "sm2p256v1";
|
||||
this.publicKeyFormat = "UNCOMPRESSED_EC_POINT_HEX";
|
||||
this.privateKeyFormat = "SM2_PRIVATE_SCALAR_HEX";
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public long getRecordId() { return recordId; }
|
||||
public String getKeyId() { return keyId; }
|
||||
public String getName() { return name; }
|
||||
public String getOwner() { return owner; }
|
||||
public String getPurpose() { return purpose; }
|
||||
public long getUserId() { return userId; }
|
||||
public long getSchoolId() { return schoolId; }
|
||||
public long getClassId() { return classId; }
|
||||
public String getAlgorithm() { return algorithm; }
|
||||
public String getSignatureHashAlgorithm() { return signatureHashAlgorithm; }
|
||||
public String getSignatureAlgorithm() { return signatureAlgorithm; }
|
||||
public String getCurve() { return curve; }
|
||||
public String getPublicKeyFormat() { return publicKeyFormat; }
|
||||
public String getPrivateKeyFormat() { return privateKeyFormat; }
|
||||
public String getPublicKey() { return publicKey; }
|
||||
public String getPrivateKey() { return privateKey; }
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionPrivateKeyCipher;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionSm2KeyAuditEntity;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionSm2KeyAuditMapper;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionSm2KeyEntity;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionSm2KeyMapper;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
public class InstitutionKeyService {
|
||||
public static final String BANK_SECOND_KEY = "BANK_SECOND_SM2_KEY";
|
||||
public static final String CENTRAL_FIRST_KEY = "CENTRAL_FIRST_SM2_KEY";
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentityCryptography cryptography;
|
||||
@Resource
|
||||
private InstitutionPrivateKeyCipher privateKeyCipher;
|
||||
@Resource
|
||||
private InstitutionSm2KeyMapper keyMapper;
|
||||
@Resource
|
||||
private InstitutionSm2KeyAuditMapper auditMapper;
|
||||
|
||||
@Transactional
|
||||
public InstitutionKeyPairResult commercialBankKey(InstitutionKeySubject subject, String operator) {
|
||||
return findOrCreate(subject, BANK_SECOND_KEY, "商业银行第二密钥", "COMMERCIAL_BANK",
|
||||
"SIGN_APPLICATION", operator);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InstitutionKeyPairResult centralBankKey(InstitutionKeySubject subject, String operator) {
|
||||
return findOrCreate(subject, CENTRAL_FIRST_KEY, "中央银行第一密钥", "CENTRAL_BANK",
|
||||
"SIGN_IDENTIFIER", operator);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public String signCommercialBank(InstitutionKeySubject subject, String message) {
|
||||
return cryptography.sign(privateKey(subject, BANK_SECOND_KEY), message);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public boolean verifyCommercialBank(InstitutionKeySubject subject, String message, String signature) {
|
||||
return cryptography.verify(required(subject, BANK_SECOND_KEY).getPublicKey(), message, signature);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public String signCentralBank(InstitutionKeySubject subject, String message) {
|
||||
return cryptography.sign(privateKey(subject, CENTRAL_FIRST_KEY), message);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void logicalDelete(InstitutionKeySubject subject, String keyId, String operator) {
|
||||
InstitutionSm2KeyEntity key = required(subject, keyId);
|
||||
LambdaUpdateWrapper<InstitutionSm2KeyEntity> update = new LambdaUpdateWrapper<InstitutionSm2KeyEntity>()
|
||||
.eq(InstitutionSm2KeyEntity::getId, key.getId())
|
||||
.eq(InstitutionSm2KeyEntity::getDeleted, false)
|
||||
.set(InstitutionSm2KeyEntity::getDeleted, true)
|
||||
.set(InstitutionSm2KeyEntity::getUpdatedAt, LocalDateTime.now())
|
||||
.set(InstitutionSm2KeyEntity::getUpdatedBy, operator);
|
||||
if (keyMapper.update(null, update) != 1) {
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "密钥已删除或状态已变化");
|
||||
}
|
||||
audit(key, subject, "DELETE", "逻辑删除机构SM2密钥", operator);
|
||||
}
|
||||
|
||||
private InstitutionKeyPairResult findOrCreate(InstitutionKeySubject subject, String keyId, String name,
|
||||
String owner, String purpose, String operator) {
|
||||
InstitutionSm2KeyEntity key = find(subject, keyId);
|
||||
if (key == null) {
|
||||
InstitutionSm2KeyPair pair = cryptography.generateSm2KeyPair();
|
||||
key = new InstitutionSm2KeyEntity();
|
||||
key.setKeyId(keyId);
|
||||
key.setKeyName(name);
|
||||
key.setKeyOwner(owner);
|
||||
key.setKeyPurpose(purpose);
|
||||
key.setUserId(subject.getUserId());
|
||||
key.setSchoolId(subject.getSchoolId());
|
||||
key.setClassId(subject.getClassId());
|
||||
key.setPublicKey(pair.getPublicKey());
|
||||
key.setEncryptedPrivateKey(privateKeyCipher.encrypt(pair.getPrivateKey()));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
key.setCreatedAt(now);
|
||||
key.setUpdatedAt(now);
|
||||
key.setCreatedBy(operator);
|
||||
key.setUpdatedBy(operator);
|
||||
key.setDeleted(false);
|
||||
keyMapper.insert(key);
|
||||
audit(key, subject, "CREATE", "生成并保存机构SM2密钥对", operator);
|
||||
} else {
|
||||
audit(key, subject, "VIEW_PRIVATE_KEY", "受控查看机构SM2公私钥", operator);
|
||||
}
|
||||
return result(key, subject);
|
||||
}
|
||||
|
||||
private String privateKey(InstitutionKeySubject subject, String keyId) {
|
||||
return privateKeyCipher.decrypt(required(subject, keyId).getEncryptedPrivateKey());
|
||||
}
|
||||
|
||||
private InstitutionSm2KeyEntity required(InstitutionKeySubject subject, String keyId) {
|
||||
InstitutionSm2KeyEntity key = find(subject, keyId);
|
||||
if (key == null) {
|
||||
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "当前用户、学校和班级下的密钥不存在,请先获取密钥");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private InstitutionSm2KeyEntity find(InstitutionKeySubject subject, String keyId) {
|
||||
return keyMapper.selectOne(new LambdaQueryWrapper<InstitutionSm2KeyEntity>()
|
||||
.eq(InstitutionSm2KeyEntity::getKeyId, keyId)
|
||||
.eq(InstitutionSm2KeyEntity::getUserId, subject.getUserId())
|
||||
.eq(InstitutionSm2KeyEntity::getSchoolId, subject.getSchoolId())
|
||||
.eq(InstitutionSm2KeyEntity::getClassId, subject.getClassId())
|
||||
.eq(InstitutionSm2KeyEntity::getDeleted, false)
|
||||
.orderByDesc(InstitutionSm2KeyEntity::getCreatedAt)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
private InstitutionKeyPairResult result(InstitutionSm2KeyEntity key, InstitutionKeySubject subject) {
|
||||
return new InstitutionKeyPairResult(key.getId(), key.getKeyId(), key.getKeyName(), key.getKeyOwner(),
|
||||
key.getKeyPurpose(), subject, key.getPublicKey(), privateKeyCipher.decrypt(key.getEncryptedPrivateKey()));
|
||||
}
|
||||
|
||||
private void audit(InstitutionSm2KeyEntity key, InstitutionKeySubject subject, String operation,
|
||||
String detail, String operator) {
|
||||
InstitutionSm2KeyAuditEntity audit = new InstitutionSm2KeyAuditEntity();
|
||||
audit.setKeyRecordId(key.getId());
|
||||
audit.setKeyId(key.getKeyId());
|
||||
audit.setOperation(operation);
|
||||
audit.setUserId(subject.getUserId());
|
||||
audit.setSchoolId(subject.getSchoolId());
|
||||
audit.setClassId(subject.getClassId());
|
||||
audit.setOperationDetail(detail);
|
||||
audit.setCreatedAt(LocalDateTime.now());
|
||||
audit.setCreatedBy(operator);
|
||||
auditMapper.insert(audit);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
public class InstitutionKeySubject {
|
||||
private final long userId;
|
||||
private final long schoolId;
|
||||
private final long classId;
|
||||
|
||||
public InstitutionKeySubject(long userId, long schoolId, long classId) {
|
||||
this.userId = userId;
|
||||
this.schoolId = schoolId;
|
||||
this.classId = classId;
|
||||
}
|
||||
|
||||
public static InstitutionKeySubject from(Jwt jwt) {
|
||||
return new InstitutionKeySubject(requiredLong(jwt, "userId"), requiredLong(jwt, "schoolId"),
|
||||
requiredLong(jwt, "classId"));
|
||||
}
|
||||
|
||||
private static long requiredLong(Jwt jwt, String claim) {
|
||||
Object value = jwt.getClaims().get(claim);
|
||||
if (value == null) {
|
||||
throw new BusinessException(ErrorCode.UNAUTHORIZED, "Token缺少" + claim);
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(String.valueOf(value));
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new BusinessException(ErrorCode.UNAUTHORIZED, "Token中的" + claim + "格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
public long getUserId() { return userId; }
|
||||
public long getSchoolId() { return schoolId; }
|
||||
public long getClassId() { return classId; }
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionPublicKeyResult {
|
||||
private final String keyId;
|
||||
private final String name;
|
||||
private final String owner;
|
||||
private final String purpose;
|
||||
private final String algorithm;
|
||||
private final String signatureHashAlgorithm;
|
||||
private final String signatureAlgorithm;
|
||||
private final String curve;
|
||||
private final String publicKeyFormat;
|
||||
private final String publicKey;
|
||||
|
||||
public InstitutionPublicKeyResult(String keyId, String name, String owner, String purpose, String publicKey) {
|
||||
this.keyId = keyId;
|
||||
this.name = name;
|
||||
this.owner = owner;
|
||||
this.purpose = purpose;
|
||||
this.algorithm = "SM2";
|
||||
this.signatureHashAlgorithm = "SM3";
|
||||
this.signatureAlgorithm = "SM2_WITH_SM3";
|
||||
this.curve = "sm2p256v1";
|
||||
this.publicKeyFormat = "UNCOMPRESSED_EC_POINT_HEX";
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getPurpose() {
|
||||
return purpose;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public String getSignatureHashAlgorithm() {
|
||||
return signatureHashAlgorithm;
|
||||
}
|
||||
|
||||
public String getSignatureAlgorithm() {
|
||||
return signatureAlgorithm;
|
||||
}
|
||||
|
||||
public String getCurve() {
|
||||
return curve;
|
||||
}
|
||||
|
||||
public String getPublicKeyFormat() {
|
||||
return publicKeyFormat;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
public class InstitutionSignatureResult {
|
||||
private final long applicationId;
|
||||
private final String status;
|
||||
private final String algorithm;
|
||||
private final String signatureHashAlgorithm;
|
||||
private final String signatureAlgorithm;
|
||||
private final String keyId;
|
||||
private final String keyPurpose;
|
||||
private final String signature;
|
||||
|
||||
public InstitutionSignatureResult(long applicationId, String status, String algorithm, String keyId,
|
||||
String keyPurpose, String signature) {
|
||||
this.applicationId = applicationId;
|
||||
this.status = status;
|
||||
this.algorithm = algorithm;
|
||||
this.signatureHashAlgorithm = "SM3";
|
||||
this.signatureAlgorithm = "SM2_WITH_SM3";
|
||||
this.keyId = keyId;
|
||||
this.keyPurpose = keyPurpose;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public String getSignatureHashAlgorithm() {
|
||||
return signatureHashAlgorithm;
|
||||
}
|
||||
|
||||
public String getSignatureAlgorithm() {
|
||||
return signatureAlgorithm;
|
||||
}
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public String getKeyPurpose() {
|
||||
return keyPurpose;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.yau.digitalrmb.institutionidentity.application;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationEntity;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierApplicationMapper;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierTrainingErrorEntity;
|
||||
import com.yau.digitalrmb.institutionidentity.infrastructure.InstitutionIdentifierTrainingErrorMapper;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
public class InstitutionTrainingErrorRecorder {
|
||||
@Resource
|
||||
private InstitutionIdentifierApplicationMapper applicationMapper;
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentifierTrainingErrorMapper errorMapper;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public int record(long applicationId, String errorType, String operationStep, String providedKeyId,
|
||||
String errorMessage, String operator) {
|
||||
InstitutionIdentifierApplicationEntity application = applicationMapper.selectById(applicationId);
|
||||
if (application == null || Boolean.TRUE.equals(application.getDeleted())) {
|
||||
throw new BusinessException(ErrorCode.RESOURCE_NOT_FOUND, "机构标识申请不存在");
|
||||
}
|
||||
|
||||
LambdaUpdateWrapper<InstitutionIdentifierApplicationEntity> increment =
|
||||
new LambdaUpdateWrapper<InstitutionIdentifierApplicationEntity>()
|
||||
.eq(InstitutionIdentifierApplicationEntity::getId, applicationId)
|
||||
.eq(InstitutionIdentifierApplicationEntity::getScoringCriteria,
|
||||
application.getScoringCriteria())
|
||||
.eq(InstitutionIdentifierApplicationEntity::getDeleted, false)
|
||||
.setSql("scoring_criteria = scoring_criteria + 1")
|
||||
.set(InstitutionIdentifierApplicationEntity::getUpdatedAt, LocalDateTime.now())
|
||||
.set(InstitutionIdentifierApplicationEntity::getUpdatedBy, operator);
|
||||
if (applicationMapper.update(null, increment) != 1) {
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "错误次数已变化,请重新操作");
|
||||
}
|
||||
|
||||
int errorSequence = application.getScoringCriteria() + 1;
|
||||
InstitutionIdentifierTrainingErrorEntity error = new InstitutionIdentifierTrainingErrorEntity();
|
||||
error.setApplicationId(applicationId);
|
||||
error.setTrainingRound(application.getTrainingRound());
|
||||
error.setErrorSequence(errorSequence);
|
||||
error.setErrorType(errorType);
|
||||
error.setOperationStep(operationStep);
|
||||
error.setApplicationStatus(application.getStatus());
|
||||
error.setProvidedKeyId(providedKeyId);
|
||||
error.setErrorMessage(errorMessage);
|
||||
error.setCreatedAt(LocalDateTime.now());
|
||||
error.setCreatedBy(operator);
|
||||
errorMapper.insert(error);
|
||||
return errorSequence;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
package com.yau.digitalrmb.institutionidentity.domain;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class InstitutionIdentifierApplication {
|
||||
private static final Pattern BANK_CODE = Pattern.compile("[A-Z0-9]{8,32}");
|
||||
|
||||
public enum Status {
|
||||
PREPARED,
|
||||
DIGESTED,
|
||||
SIGNED,
|
||||
PACKAGED,
|
||||
VERIFIED,
|
||||
VERIFICATION_FAILED,
|
||||
ISSUED,
|
||||
REJECTED
|
||||
}
|
||||
|
||||
private final Long id;
|
||||
private final String bankCode;
|
||||
private final String timestamp;
|
||||
private String digest;
|
||||
private String bankSignature;
|
||||
private Status status;
|
||||
private String verificationKeyId;
|
||||
private Boolean signatureValid;
|
||||
private Boolean digestMatches;
|
||||
private String signingKeyId;
|
||||
private String institutionIdentifier;
|
||||
private String centralBankSignature;
|
||||
|
||||
public InstitutionIdentifierApplication(Long id, String bankCode, String timestamp, String digest,
|
||||
String bankSignature, Status status, String verificationKeyId,
|
||||
Boolean signatureValid, Boolean digestMatches, String signingKeyId,
|
||||
String institutionIdentifier, String centralBankSignature) {
|
||||
validateBase(bankCode, timestamp);
|
||||
this.id = id;
|
||||
this.bankCode = bankCode;
|
||||
this.timestamp = timestamp;
|
||||
this.digest = digest;
|
||||
this.bankSignature = bankSignature;
|
||||
this.status = status;
|
||||
this.verificationKeyId = verificationKeyId;
|
||||
this.signatureValid = signatureValid;
|
||||
this.digestMatches = digestMatches;
|
||||
this.signingKeyId = signingKeyId;
|
||||
this.institutionIdentifier = institutionIdentifier;
|
||||
this.centralBankSignature = centralBankSignature;
|
||||
}
|
||||
|
||||
public static InstitutionIdentifierApplication prepare(String bankCode, String timestamp) {
|
||||
return new InstitutionIdentifierApplication(null, bankCode, timestamp, null, null, Status.PREPARED,
|
||||
null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static InstitutionIdentifierApplication restore(Long id, String bankCode, String timestamp, String digest,
|
||||
String bankSignature, Status status, String verificationKeyId, Boolean signatureValid,
|
||||
Boolean digestMatches, String signingKeyId, String institutionIdentifier, String centralBankSignature) {
|
||||
return new InstitutionIdentifierApplication(id, bankCode, timestamp, digest, bankSignature, status,
|
||||
verificationKeyId, signatureValid, digestMatches, signingKeyId, institutionIdentifier,
|
||||
centralBankSignature);
|
||||
}
|
||||
|
||||
public void recordDigest(String digest) {
|
||||
requireStatus(Status.PREPARED, "当前步骤不是摘要计算,不能操作");
|
||||
if (digest == null || !digest.matches("[0-9A-F]{64}")) {
|
||||
throw new IllegalArgumentException("SM3摘要格式不正确");
|
||||
}
|
||||
this.digest = digest;
|
||||
this.status = Status.DIGESTED;
|
||||
}
|
||||
|
||||
public void recordBankSignature(String bankSignature) {
|
||||
requireStatus(Status.DIGESTED, "当前步骤不是商业银行签名,不能操作");
|
||||
if (bankSignature == null || !bankSignature.matches("[0-9A-F]+")) {
|
||||
throw new IllegalArgumentException("银行SM2签名格式不正确");
|
||||
}
|
||||
this.bankSignature = bankSignature;
|
||||
this.status = Status.SIGNED;
|
||||
}
|
||||
|
||||
public void packageApplication() {
|
||||
requireStatus(Status.SIGNED, "当前步骤不是申请组装,不能操作");
|
||||
this.status = Status.PACKAGED;
|
||||
}
|
||||
|
||||
public void recordVerification(String verificationKeyId, boolean signatureValid, boolean digestMatches) {
|
||||
requireStatus(Status.PACKAGED, "当前步骤不是央行验证,不能操作");
|
||||
this.verificationKeyId = verificationKeyId;
|
||||
this.signatureValid = signatureValid;
|
||||
this.digestMatches = digestMatches;
|
||||
this.status = signatureValid && digestMatches ? Status.VERIFIED : Status.VERIFICATION_FAILED;
|
||||
}
|
||||
|
||||
public void issue(String signingKeyId, String institutionIdentifier, String centralBankSignature) {
|
||||
requireStatus(Status.VERIFIED, "申请尚未验证通过,不能进行央行签名确权");
|
||||
if (signingKeyId == null || institutionIdentifier == null || centralBankSignature == null) {
|
||||
throw new IllegalArgumentException("签发信息不能为空");
|
||||
}
|
||||
this.signingKeyId = signingKeyId;
|
||||
this.institutionIdentifier = institutionIdentifier;
|
||||
this.centralBankSignature = centralBankSignature;
|
||||
this.status = Status.ISSUED;
|
||||
}
|
||||
|
||||
public void reject() {
|
||||
if (status == Status.ISSUED || status == Status.REJECTED) {
|
||||
throw new IllegalStateException("申请状态不允许拒绝");
|
||||
}
|
||||
status = Status.REJECTED;
|
||||
}
|
||||
|
||||
private void requireStatus(Status expected, String message) {
|
||||
if (status != expected) {
|
||||
throw new IllegalStateException(message + ",当前状态:" + status.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateBase(String bankCode, String timestamp) {
|
||||
if (bankCode == null || !BANK_CODE.matcher(bankCode).matches()) {
|
||||
throw new IllegalArgumentException("银行代码格式不正确");
|
||||
}
|
||||
if (timestamp == null || !timestamp.matches("\\d{14}")) {
|
||||
throw new IllegalArgumentException("时间戳格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public String getBankCode() { return bankCode; }
|
||||
public String getTimestamp() { return timestamp; }
|
||||
public String getDigest() { return digest; }
|
||||
public String getBankSignature() { return bankSignature; }
|
||||
public Status getStatus() { return status; }
|
||||
public String getVerificationKeyId() { return verificationKeyId; }
|
||||
public Boolean getSignatureValid() { return signatureValid; }
|
||||
public Boolean getDigestMatches() { return digestMatches; }
|
||||
public String getSigningKeyId() { return signingKeyId; }
|
||||
public String getInstitutionIdentifier() { return institutionIdentifier; }
|
||||
public String getCentralBankSignature() { return centralBankSignature; }
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package com.yau.digitalrmb.institutionidentity.domain;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface InstitutionIdentifierRepository {
|
||||
long save(InstitutionIdentifierApplication application, int trainingRound, String operator);
|
||||
|
||||
Optional<InstitutionIdentifierApplication> findById(long applicationId);
|
||||
|
||||
void update(InstitutionIdentifierApplication application,
|
||||
InstitutionIdentifierApplication.Status expectedStatus,
|
||||
String operation,
|
||||
String operationDetail,
|
||||
String operator);
|
||||
|
||||
int nextTrainingRound(String bankCode, String operator);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.yau.digitalrmb.institutionidentity.domain;
|
||||
|
||||
public interface InstitutionIdentityCryptography {
|
||||
String sm3(String text);
|
||||
InstitutionSm2KeyPair generateSm2KeyPair();
|
||||
String sign(String privateKey, String message);
|
||||
boolean verify(String publicKey, String message, String signature);
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.yau.digitalrmb.institutionidentity.domain;
|
||||
|
||||
public class InstitutionSm2KeyPair {
|
||||
private final String privateKey;
|
||||
private final String publicKey;
|
||||
|
||||
public InstitutionSm2KeyPair(String privateKey, String publicKey) {
|
||||
this.privateKey = privateKey;
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() { return privateKey; }
|
||||
public String getPublicKey() { return publicKey; }
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.yau.digitalrmb.shared.infrastructure.persistence.AuditableEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
@TableName("institution_identifier_application")
|
||||
public class InstitutionIdentifierApplicationEntity extends AuditableEntity {
|
||||
@TableField("bank_code") private String bankCode;
|
||||
@TableField("request_timestamp") private String requestTimestamp;
|
||||
private String digest;
|
||||
@TableField("bank_signature") private String bankSignature;
|
||||
private String status;
|
||||
@TableField("verification_key_id") private String verificationKeyId;
|
||||
@TableField("signature_valid") private Boolean signatureValid;
|
||||
@TableField("digest_matches") private Boolean digestMatches;
|
||||
@TableField("signing_key_id") private String signingKeyId;
|
||||
@TableField("institution_identifier") private String institutionIdentifier;
|
||||
@TableField("central_bank_signature") private String centralBankSignature;
|
||||
@TableField("training_round") private Integer trainingRound;
|
||||
@TableField("scoring_criteria") private Integer scoringCriteria;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InstitutionIdentifierApplicationMapper extends BaseMapper<InstitutionIdentifierApplicationEntity> { }
|
||||
@ -0,0 +1,31 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("institution_identifier_operation_log")
|
||||
public class InstitutionIdentifierOperationLogEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("application_id")
|
||||
private Long applicationId;
|
||||
private String operation;
|
||||
@TableField("from_status")
|
||||
private String fromStatus;
|
||||
@TableField("to_status")
|
||||
private String toStatus;
|
||||
@TableField("operation_detail")
|
||||
private String operationDetail;
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
@TableField("created_by")
|
||||
private String createdBy;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InstitutionIdentifierOperationLogMapper extends BaseMapper<InstitutionIdentifierOperationLogEntity> {
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("institution_identifier_training_error")
|
||||
public class InstitutionIdentifierTrainingErrorEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("application_id")
|
||||
private Long applicationId;
|
||||
@TableField("training_round")
|
||||
private Integer trainingRound;
|
||||
@TableField("error_sequence")
|
||||
private Integer errorSequence;
|
||||
@TableField("error_type")
|
||||
private String errorType;
|
||||
@TableField("operation_step")
|
||||
private String operationStep;
|
||||
@TableField("application_status")
|
||||
private String applicationStatus;
|
||||
@TableField("provided_key_id")
|
||||
private String providedKeyId;
|
||||
@TableField("error_message")
|
||||
private String errorMessage;
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
@TableField("created_by")
|
||||
private String createdBy;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InstitutionIdentifierTrainingErrorMapper extends BaseMapper<InstitutionIdentifierTrainingErrorEntity> {
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.yau.digitalrmb.security.config.SecurityProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
@Component
|
||||
public class InstitutionPrivateKeyCipher {
|
||||
private static final int IV_LENGTH = 12;
|
||||
private static final int TAG_LENGTH = 128;
|
||||
private final SecretKeySpec encryptionKey;
|
||||
|
||||
public InstitutionPrivateKeyCipher(SecurityProperties properties) {
|
||||
try {
|
||||
byte[] digest = MessageDigest.getInstance("SHA-256")
|
||||
.digest(properties.getJwt().getSecret().getBytes(StandardCharsets.UTF_8));
|
||||
this.encryptionKey = new SecretKeySpec(digest, "AES");
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("机构私钥加密组件初始化失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public String encrypt(String privateKey) {
|
||||
try {
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new GCMParameterSpec(TAG_LENGTH, iv));
|
||||
byte[] encrypted = cipher.doFinal(privateKey.getBytes(StandardCharsets.US_ASCII));
|
||||
byte[] result = new byte[iv.length + encrypted.length];
|
||||
System.arraycopy(iv, 0, result, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
|
||||
return Base64.getEncoder().encodeToString(result);
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("机构私钥加密失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String encryptedPrivateKey) {
|
||||
try {
|
||||
byte[] value = Base64.getDecoder().decode(encryptedPrivateKey);
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
byte[] encrypted = new byte[value.length - IV_LENGTH];
|
||||
System.arraycopy(value, 0, iv, 0, iv.length);
|
||||
System.arraycopy(value, iv.length, encrypted, 0, encrypted.length);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new GCMParameterSpec(TAG_LENGTH, iv));
|
||||
return new String(cipher.doFinal(encrypted), StandardCharsets.US_ASCII);
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("机构私钥解密失败", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("institution_sm2_key_audit")
|
||||
public class InstitutionSm2KeyAuditEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@TableField("key_record_id")
|
||||
private Long keyRecordId;
|
||||
@TableField("key_id")
|
||||
private String keyId;
|
||||
private String operation;
|
||||
@TableField("user_id")
|
||||
private Long userId;
|
||||
@TableField("school_id")
|
||||
private Long schoolId;
|
||||
@TableField("class_id")
|
||||
private Long classId;
|
||||
@TableField("operation_detail")
|
||||
private String operationDetail;
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
@TableField("created_by")
|
||||
private String createdBy;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InstitutionSm2KeyAuditMapper extends BaseMapper<InstitutionSm2KeyAuditEntity> {
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.yau.digitalrmb.shared.infrastructure.persistence.AuditableEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("institution_sm2_key")
|
||||
public class InstitutionSm2KeyEntity extends AuditableEntity {
|
||||
@TableField("key_id")
|
||||
private String keyId;
|
||||
@TableField("key_name")
|
||||
private String keyName;
|
||||
@TableField("key_owner")
|
||||
private String keyOwner;
|
||||
@TableField("key_purpose")
|
||||
private String keyPurpose;
|
||||
@TableField("user_id")
|
||||
private Long userId;
|
||||
@TableField("school_id")
|
||||
private Long schoolId;
|
||||
@TableField("class_id")
|
||||
private Long classId;
|
||||
@TableField("public_key")
|
||||
private String publicKey;
|
||||
@TableField("encrypted_private_key")
|
||||
private String encryptedPrivateKey;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InstitutionSm2KeyMapper extends BaseMapper<InstitutionSm2KeyEntity> {
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierApplication;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentifierRepository;
|
||||
import com.yau.digitalrmb.shared.api.ErrorCode;
|
||||
import com.yau.digitalrmb.shared.exception.BusinessException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class MybatisInstitutionIdentifierRepository implements InstitutionIdentifierRepository {
|
||||
@Resource
|
||||
private InstitutionIdentifierApplicationMapper mapper;
|
||||
|
||||
@Resource
|
||||
private InstitutionIdentifierOperationLogMapper operationLogMapper;
|
||||
|
||||
@Override
|
||||
public long save(InstitutionIdentifierApplication application, int trainingRound, String operator) {
|
||||
InstitutionIdentifierApplicationEntity entity = toEntity(application);
|
||||
entity.setTrainingRound(trainingRound);
|
||||
entity.setScoringCriteria(0);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
entity.setCreatedAt(now);
|
||||
entity.setUpdatedAt(now);
|
||||
entity.setCreatedBy(operator);
|
||||
entity.setUpdatedBy(operator);
|
||||
entity.setDeleted(false);
|
||||
mapper.insert(entity);
|
||||
insertLog(entity.getId(), "PREPARE", null, application.getStatus().name(),
|
||||
"创建机构标识申请并生成业务时间戳", operator);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InstitutionIdentifierApplication> findById(long applicationId) {
|
||||
InstitutionIdentifierApplicationEntity entity = mapper.selectOne(
|
||||
new LambdaQueryWrapper<InstitutionIdentifierApplicationEntity>()
|
||||
.eq(InstitutionIdentifierApplicationEntity::getId, applicationId)
|
||||
.eq(InstitutionIdentifierApplicationEntity::getDeleted, false));
|
||||
return Optional.ofNullable(entity).map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(InstitutionIdentifierApplication application,
|
||||
InstitutionIdentifierApplication.Status expectedStatus,
|
||||
String operation,
|
||||
String operationDetail,
|
||||
String operator) {
|
||||
LambdaUpdateWrapper<InstitutionIdentifierApplicationEntity> update =
|
||||
new LambdaUpdateWrapper<InstitutionIdentifierApplicationEntity>()
|
||||
.eq(InstitutionIdentifierApplicationEntity::getId, application.getId())
|
||||
.eq(InstitutionIdentifierApplicationEntity::getStatus, expectedStatus.name())
|
||||
.eq(InstitutionIdentifierApplicationEntity::getDeleted, false)
|
||||
.set(InstitutionIdentifierApplicationEntity::getDigest, application.getDigest())
|
||||
.set(InstitutionIdentifierApplicationEntity::getBankSignature, application.getBankSignature())
|
||||
.set(InstitutionIdentifierApplicationEntity::getStatus, application.getStatus().name())
|
||||
.set(InstitutionIdentifierApplicationEntity::getVerificationKeyId,
|
||||
application.getVerificationKeyId())
|
||||
.set(InstitutionIdentifierApplicationEntity::getSignatureValid,
|
||||
application.getSignatureValid())
|
||||
.set(InstitutionIdentifierApplicationEntity::getDigestMatches,
|
||||
application.getDigestMatches())
|
||||
.set(InstitutionIdentifierApplicationEntity::getSigningKeyId, application.getSigningKeyId())
|
||||
.set(InstitutionIdentifierApplicationEntity::getInstitutionIdentifier,
|
||||
application.getInstitutionIdentifier())
|
||||
.set(InstitutionIdentifierApplicationEntity::getCentralBankSignature,
|
||||
application.getCentralBankSignature())
|
||||
.set(InstitutionIdentifierApplicationEntity::getUpdatedAt, LocalDateTime.now())
|
||||
.set(InstitutionIdentifierApplicationEntity::getUpdatedBy, operator);
|
||||
if (mapper.update(null, update) != 1) {
|
||||
throw new BusinessException(ErrorCode.VALIDATION_ERROR, "申请状态已变化,不能重复或越级操作");
|
||||
}
|
||||
insertLog(application.getId(), operation, expectedStatus.name(), application.getStatus().name(),
|
||||
operationDetail, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextTrainingRound(String bankCode, String operator) {
|
||||
InstitutionIdentifierApplicationEntity latest = mapper.selectOne(
|
||||
new LambdaQueryWrapper<InstitutionIdentifierApplicationEntity>()
|
||||
.eq(InstitutionIdentifierApplicationEntity::getBankCode, bankCode)
|
||||
.eq(InstitutionIdentifierApplicationEntity::getCreatedBy, operator)
|
||||
.eq(InstitutionIdentifierApplicationEntity::getDeleted, false)
|
||||
.orderByDesc(InstitutionIdentifierApplicationEntity::getTrainingRound)
|
||||
.last("LIMIT 1"));
|
||||
return latest == null || latest.getTrainingRound() == null ? 1 : latest.getTrainingRound() + 1;
|
||||
}
|
||||
|
||||
private InstitutionIdentifierApplicationEntity toEntity(InstitutionIdentifierApplication application) {
|
||||
InstitutionIdentifierApplicationEntity entity = new InstitutionIdentifierApplicationEntity();
|
||||
entity.setId(application.getId());
|
||||
entity.setBankCode(application.getBankCode());
|
||||
entity.setRequestTimestamp(application.getTimestamp());
|
||||
entity.setDigest(application.getDigest());
|
||||
entity.setBankSignature(application.getBankSignature());
|
||||
entity.setStatus(application.getStatus().name());
|
||||
entity.setVerificationKeyId(application.getVerificationKeyId());
|
||||
entity.setSignatureValid(application.getSignatureValid());
|
||||
entity.setDigestMatches(application.getDigestMatches());
|
||||
entity.setSigningKeyId(application.getSigningKeyId());
|
||||
entity.setInstitutionIdentifier(application.getInstitutionIdentifier());
|
||||
entity.setCentralBankSignature(application.getCentralBankSignature());
|
||||
return entity;
|
||||
}
|
||||
|
||||
private InstitutionIdentifierApplication toDomain(InstitutionIdentifierApplicationEntity entity) {
|
||||
return InstitutionIdentifierApplication.restore(entity.getId(), entity.getBankCode(),
|
||||
entity.getRequestTimestamp(), entity.getDigest(), entity.getBankSignature(),
|
||||
InstitutionIdentifierApplication.Status.valueOf(entity.getStatus()), entity.getVerificationKeyId(),
|
||||
entity.getSignatureValid(), entity.getDigestMatches(), entity.getSigningKeyId(),
|
||||
entity.getInstitutionIdentifier(), entity.getCentralBankSignature());
|
||||
}
|
||||
|
||||
private void insertLog(Long applicationId, String operation, String fromStatus, String toStatus,
|
||||
String operationDetail, String operator) {
|
||||
InstitutionIdentifierOperationLogEntity log = new InstitutionIdentifierOperationLogEntity();
|
||||
log.setApplicationId(applicationId);
|
||||
log.setOperation(operation);
|
||||
log.setFromStatus(fromStatus);
|
||||
log.setToStatus(toStatus);
|
||||
log.setOperationDetail(operationDetail);
|
||||
log.setCreatedAt(LocalDateTime.now());
|
||||
log.setCreatedBy(operator);
|
||||
operationLogMapper.insert(log);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package com.yau.digitalrmb.institutionidentity.infrastructure;
|
||||
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionIdentityCryptography;
|
||||
import com.yau.digitalrmb.institutionidentity.domain.InstitutionSm2KeyPair;
|
||||
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithID;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.SM2Signer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@Component
|
||||
public class SmInstitutionIdentityCryptography implements InstitutionIdentityCryptography {
|
||||
private static final byte[] USER_ID = "1234567812345678".getBytes(StandardCharsets.US_ASCII);
|
||||
private static final ECDomainParameters DOMAIN;
|
||||
|
||||
static {
|
||||
X9ECParameters curve = GMNamedCurves.getByName("sm2p256v1");
|
||||
DOMAIN = new ECDomainParameters(curve.getCurve(), curve.getG(), curve.getN(), curve.getH());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sm3(String text) {
|
||||
byte[] input = text.getBytes(StandardCharsets.UTF_8);
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.update(input, 0, input.length);
|
||||
byte[] output = new byte[digest.getDigestSize()];
|
||||
digest.doFinal(output, 0);
|
||||
return hex(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstitutionSm2KeyPair generateSm2KeyPair() {
|
||||
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||
generator.init(new ECKeyGenerationParameters(DOMAIN, new SecureRandom()));
|
||||
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
|
||||
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
|
||||
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
|
||||
return new InstitutionSm2KeyPair(leftPad(privateKey.getD().toString(16).toUpperCase(), 64),
|
||||
hex(publicKey.getQ().getEncoded(false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sign(String privateKey, String message) {
|
||||
ECPrivateKeyParameters key = new ECPrivateKeyParameters(new BigInteger(privateKey, 16), DOMAIN);
|
||||
SM2Signer signer = new SM2Signer();
|
||||
signer.init(true, new ParametersWithID(new ParametersWithRandom(key, new SecureRandom()), USER_ID));
|
||||
byte[] bytes = message.getBytes(StandardCharsets.US_ASCII);
|
||||
signer.update(bytes, 0, bytes.length);
|
||||
try {
|
||||
return hex(signer.generateSignature());
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("SM2签名计算失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String publicKey, String message, String signature) {
|
||||
try {
|
||||
ECPublicKeyParameters key = new ECPublicKeyParameters(DOMAIN.getCurve().decodePoint(fromHex(publicKey)),
|
||||
DOMAIN);
|
||||
SM2Signer signer = new SM2Signer();
|
||||
signer.init(false, new ParametersWithID(key, USER_ID));
|
||||
byte[] bytes = message.getBytes(StandardCharsets.US_ASCII);
|
||||
signer.update(bytes, 0, bytes.length);
|
||||
return signer.verifySignature(fromHex(signature));
|
||||
} catch (RuntimeException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] fromHex(String value) {
|
||||
if (value == null || (value.length() & 1) == 1) {
|
||||
throw new IllegalArgumentException("非法十六进制");
|
||||
}
|
||||
byte[] bytes = new byte[value.length() / 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) Integer.parseInt(value.substring(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static String leftPad(String value, int length) {
|
||||
StringBuilder result = new StringBuilder(length);
|
||||
for (int i = value.length(); i < length; i++) {
|
||||
result.append('0');
|
||||
}
|
||||
return result.append(value).toString();
|
||||
}
|
||||
|
||||
private static String hex(byte[] bytes) {
|
||||
StringBuilder result = new StringBuilder(bytes.length * 2);
|
||||
for (byte value : bytes) {
|
||||
result.append(String.format("%02X", value));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class ConfirmInstitutionIdentifierRequest {
|
||||
@NotNull(message = "申请编号不能为空")
|
||||
private Long applicationId;
|
||||
|
||||
@NotBlank(message = "签名密钥不能为空")
|
||||
private String signingKeyId;
|
||||
|
||||
public Long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(Long applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
public String getSigningKeyId() {
|
||||
return signingKeyId;
|
||||
}
|
||||
|
||||
public void setSigningKeyId(String signingKeyId) {
|
||||
this.signingKeyId = signingKeyId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
public class GenerateInstitutionIdentifierRequest {
|
||||
@NotBlank(message = "银行代码不能为空")
|
||||
@Pattern(regexp = "[A-Za-z0-9]{8,32}", message = "银行代码必须为8至32位字母或数字")
|
||||
private String bankCode;
|
||||
public String getBankCode() { return bankCode; }
|
||||
public void setBankCode(String bankCode) { this.bankCode = bankCode; }
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class InstitutionIdentifierStepRequest {
|
||||
@NotNull(message = "申请编号不能为空")
|
||||
private Long applicationId;
|
||||
|
||||
private String keyId;
|
||||
|
||||
public Long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(Long applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(String keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.yau.digitalrmb.institutionidentity.interfaces.dto;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class VerifyInstitutionApplicationRequest {
|
||||
@NotNull(message = "申请编号不能为空")
|
||||
private Long applicationId;
|
||||
|
||||
@NotBlank(message = "验证密钥不能为空")
|
||||
private String verificationKeyId;
|
||||
|
||||
public Long getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
|
||||
public void setApplicationId(Long applicationId) {
|
||||
this.applicationId = applicationId;
|
||||
}
|
||||
|
||||
public String getVerificationKeyId() {
|
||||
return verificationKeyId;
|
||||
}
|
||||
|
||||
public void setVerificationKeyId(String verificationKeyId) {
|
||||
this.verificationKeyId = verificationKeyId;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue