审计日志签名
This commit is contained in:
parent
06a76d5f39
commit
9ec88c3093
@ -12,7 +12,14 @@ import com.cisd.tms.modules.log.enums.*;
|
|||||||
import com.cisd.tms.modules.log.repository.OperationAuditLogRepository;
|
import com.cisd.tms.modules.log.repository.OperationAuditLogRepository;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bouncycastle.asn1.*;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
|
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||||
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -21,12 +28,17 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -36,7 +48,13 @@ import java.util.UUID;
|
|||||||
public class OperationAuditService {
|
public class OperationAuditService {
|
||||||
|
|
||||||
private static final String PROVIDER = "BC";
|
private static final String PROVIDER = "BC";
|
||||||
private static final String SIGNATURE_ALGO = "SM3withSM2";
|
private static final String SM2_CURVE = "sm2p256v1";
|
||||||
|
|
||||||
|
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
|
||||||
|
private static final int GM0018_ECCREF_PUBLIC_KEY_LENGTH = 132;
|
||||||
|
private static final int GM0018_ECCREF_COMPONENT_LENGTH = 64;
|
||||||
|
private static final int SM2_COORDINATE_LENGTH = 32;
|
||||||
|
|
||||||
private static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE";
|
private static final String ATTR_ROLE_CODE = "CURRENT_ROLE_CODE";
|
||||||
private static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL";
|
private static final String ATTR_AUTH_LEVEL = "CURRENT_AUTH_LEVEL";
|
||||||
|
|
||||||
@ -191,14 +209,17 @@ public class OperationAuditService {
|
|||||||
throw new IllegalArgumentException("logId不能为空");
|
throw new IllegalArgumentException("logId不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OperationAuditLogEntity existLog = operationAuditLogRepository.findByLogId(logId)
|
OperationAuditLogEntity existLog = operationAuditLogRepository.findByLogId(logId)
|
||||||
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "未找到该logId"));
|
.orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "未找到该logId"));
|
||||||
|
|
||||||
if (!AuditStatus.PENDING.equals(existLog.getAuditStatus())) {
|
if (!AuditStatus.PENDING.equals(existLog.getAuditStatus())) {
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "该日志已审计,禁止重复操作");
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "该日志已审计,禁止重复操作");
|
||||||
}
|
}
|
||||||
|
String d = buildSignValueSource(existLog, req.getAuditResult());
|
||||||
|
System.out.println(d);
|
||||||
String auditSign = trim(req.getAuditSign());
|
String auditSign = trim(req.getAuditSign());
|
||||||
|
|
||||||
if (auditSign.isEmpty()){
|
if (auditSign.isEmpty()){
|
||||||
throw new IllegalArgumentException("签名值不能为空");
|
throw new IllegalArgumentException("签名值不能为空");
|
||||||
}
|
}
|
||||||
@ -214,7 +235,7 @@ public class OperationAuditService {
|
|||||||
RoleUkeyBindingEntity roleUkeyBindingEntity = list.get(0);
|
RoleUkeyBindingEntity roleUkeyBindingEntity = list.get(0);
|
||||||
String publicKey = roleUkeyBindingEntity.getUkeyPubkey();
|
String publicKey = roleUkeyBindingEntity.getUkeyPubkey();
|
||||||
try{
|
try{
|
||||||
verifySm2Signature(signValueSource, auditSign, publicKey);
|
verifySm2Signature(publicKey, signValueSource, auditSign);
|
||||||
} catch (Exception e){
|
} catch (Exception e){
|
||||||
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage());
|
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage());
|
||||||
}
|
}
|
||||||
@ -240,57 +261,294 @@ public class OperationAuditService {
|
|||||||
|
|
||||||
|
|
||||||
private static String buildSignValueSource(OperationAuditLogEntity existLog, AuditResult auditResult) {
|
private static String buildSignValueSource(OperationAuditLogEntity existLog, AuditResult auditResult) {
|
||||||
String signValueSource = trim(existLog.getLogId()) + "_" +
|
String signValueSource = existLog.getLogId() + "_" +
|
||||||
existLog.getOperatorRoleCode() + "_" +
|
existLog.getOperatorRoleCode() + "_" +
|
||||||
existLog.getOperatorAuthLevel() + "_" +
|
existLog.getOperatorAuthLevel() + "_" +
|
||||||
existLog.getModuleCode() + "_" +
|
existLog.getModuleCode() + "_" +
|
||||||
existLog.getActionType() + "_" +
|
existLog.getActionType() + "_" +
|
||||||
trim(existLog.getRemoteIp()) + "_" +
|
existLog.getRemoteIp() + "_" +
|
||||||
existLog.getOperationResult() + "_" +
|
existLog.getOperationResult() + "_" +
|
||||||
trim(existLog.getSummary()) + "_" +
|
existLog.getSummary() + "_" +
|
||||||
trim(existLog.getErrorMessage()) + "_" +
|
existLog.getErrorMessage() + "_" +
|
||||||
existLog.getAuditStatus() + "_" +
|
existLog.getAuditStatus() + "_" +
|
||||||
auditResult + "_" +
|
auditResult + "_" +
|
||||||
trim(existLog.getSignValue()) + "_" +
|
existLog.getSignValue() + "_" +
|
||||||
existLog.getOccurredAt();
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(existLog.getOccurredAt());
|
||||||
|
|
||||||
return signValueSource;
|
return signValueSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean verifySm2Signature(String data, String signatureBase64, String publicKeyBase64) {
|
public void verifySm2Signature(String pubKey, String data, String sign) {
|
||||||
try {
|
try {
|
||||||
byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
|
PublicKey publicKey = parsePublicKey(pubKey);
|
||||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
|
|
||||||
java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("EC", PROVIDER);
|
|
||||||
PublicKey publicKey = keyFactory.generatePublic(keySpec);
|
|
||||||
|
|
||||||
Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER);
|
if (data == null || data.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("data不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signatureBytes = decodeSignature(sign);
|
||||||
|
|
||||||
|
// String signData = sm3Hex(loginSignData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
||||||
signature.initVerify(publicKey);
|
signature.initVerify(publicKey);
|
||||||
|
|
||||||
|
|
||||||
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
||||||
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
|
|
||||||
return signature.verify(signatureBytes);
|
if (!signature.verify(signatureBytes)) {
|
||||||
} catch (Exception e) {
|
throw new BizException(
|
||||||
throw new RuntimeException("SM2验签失败", e);
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"sm2验签失败"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (BizException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage());
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"signature verification 执行失败"
|
||||||
|
);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"signature verification 执行失败"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] decodeBlob(String value, String message) {
|
|
||||||
String normalized = value == null ? "" : value.trim();
|
|
||||||
if (normalized.isEmpty()) {
|
private byte[] decodeSignature(String value) {
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
|
if (value == null || value.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("signature不能为空");
|
||||||
}
|
}
|
||||||
if (normalized.matches("(?i)^[0-9a-f]+$") && normalized.length() % 2 == 0) {
|
|
||||||
return Hex.decode(normalized);
|
byte[] decoded = decodeHexOrBase64(normalize(value), "签名无效");
|
||||||
|
|
||||||
|
if (isDerSignature(decoded)) {
|
||||||
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (decoded.length == GM0018_ECCREF_COMPONENT_LENGTH * 2) {
|
||||||
|
return rawRsToDer(gm0018SignatureToRawRs(decoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.length == 64) {
|
||||||
|
return rawRsToDer(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("签名无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PublicKey parsePublicKey(String value) throws Exception {
|
||||||
|
String trimmed = value == null ? "" : value.trim();
|
||||||
|
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.contains("BEGIN")) {
|
||||||
|
return parsePemPublicKey(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = normalize(trimmed);
|
||||||
|
byte[] decoded = decodeHexOrBase64(normalized, "公钥无效");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Base64.getDecoder().decode(normalized);
|
return KeyFactory
|
||||||
} catch (IllegalArgumentException ex) {
|
.getInstance("EC", PROVIDER)
|
||||||
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), message);
|
.generatePublic(new X509EncodedKeySpec(decoded));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return parseUncompressedPointPublicKey(toPointBytes(decoded));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PublicKey parsePemPublicKey(String pemContent) throws Exception {
|
||||||
|
try (PEMParser parser = new PEMParser(new StringReader(pemContent))) {
|
||||||
|
Object parsed = parser.readObject();
|
||||||
|
|
||||||
|
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
|
||||||
|
throw new IllegalArgumentException("公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JcaPEMKeyConverter()
|
||||||
|
.setProvider(PROVIDER)
|
||||||
|
.getPublicKey(publicKeyInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey parseUncompressedPointPublicKey(byte[] pointBytes) throws Exception {
|
||||||
|
if (pointBytes.length == 64) {
|
||||||
|
byte[] rawPointBytes = pointBytes;
|
||||||
|
pointBytes = new byte[65];
|
||||||
|
pointBytes[0] = 0x04;
|
||||||
|
System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
|
||||||
|
throw new IllegalArgumentException("公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE);
|
||||||
|
|
||||||
|
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(
|
||||||
|
parameterSpec.getCurve().decodePoint(pointBytes),
|
||||||
|
parameterSpec
|
||||||
|
);
|
||||||
|
|
||||||
|
return KeyFactory
|
||||||
|
.getInstance("EC", PROVIDER)
|
||||||
|
.generatePublic(publicKeySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isDerSignature(byte[] signatureBytes) {
|
||||||
|
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
|
||||||
|
ASN1Primitive primitive = inputStream.readObject();
|
||||||
|
|
||||||
|
if (!(primitive instanceof ASN1Sequence sequence) || sequence.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence.getObjectAt(0) instanceof ASN1Integer
|
||||||
|
&& sequence.getObjectAt(1) instanceof ASN1Integer;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] rawRsToDer(byte[] rawSignature) {
|
||||||
|
if (rawSignature.length != 64) {
|
||||||
|
throw new IllegalArgumentException("签名无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] r = Arrays.copyOfRange(rawSignature, 0, 32);
|
||||||
|
byte[] s = Arrays.copyOfRange(rawSignature, 32, 64);
|
||||||
|
|
||||||
|
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||||
|
vector.add(new ASN1Integer(new BigInteger(1, r)));
|
||||||
|
vector.add(new ASN1Integer(new BigInteger(1, s)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new DERSequence(vector).getEncoded(ASN1Encoding.DER);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException("签名无效", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] toPointBytes(byte[] keyBytes) {
|
||||||
|
if (keyBytes.length == GM0018_ECCREF_PUBLIC_KEY_LENGTH) {
|
||||||
|
return gm0018EccRefToPointBytes(keyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] gm0018EccRefToPointBytes(byte[] keyBytes) {
|
||||||
|
int bitsLittleEndian = (keyBytes[0] & 0xff)
|
||||||
|
| ((keyBytes[1] & 0xff) << 8)
|
||||||
|
| ((keyBytes[2] & 0xff) << 16)
|
||||||
|
| ((keyBytes[3] & 0xff) << 24);
|
||||||
|
|
||||||
|
int bitsBigEndian = ((keyBytes[0] & 0xff) << 24)
|
||||||
|
| ((keyBytes[1] & 0xff) << 16)
|
||||||
|
| ((keyBytes[2] & 0xff) << 8)
|
||||||
|
| (keyBytes[3] & 0xff);
|
||||||
|
|
||||||
|
if (bitsLittleEndian != 256 && bitsBigEndian != 256) {
|
||||||
|
throw new IllegalArgumentException("公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pointBytes = new byte[65];
|
||||||
|
pointBytes[0] = 0x04;
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
keyBytes,
|
||||||
|
4 + GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||||
|
pointBytes,
|
||||||
|
1,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
keyBytes,
|
||||||
|
4 + GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||||
|
pointBytes,
|
||||||
|
1 + SM2_COORDINATE_LENGTH,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
return pointBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) {
|
||||||
|
byte[] rawSignature = new byte[64];
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
signatureBytes,
|
||||||
|
GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||||
|
rawSignature,
|
||||||
|
0,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
signatureBytes,
|
||||||
|
GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||||
|
rawSignature,
|
||||||
|
SM2_COORDINATE_LENGTH,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
return rawSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] decodeHexOrBase64(String value, String message) {
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.matches("(?i)^[0-9a-f]+$") && value.length() % 2 == 0) {
|
||||||
|
return Hex.decode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Base64.getDecoder().decode(value);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new IllegalArgumentException(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String normalize(String value) {
|
||||||
|
String normalized = value == null ? "" : value.trim();
|
||||||
|
|
||||||
|
if (!normalized.contains("BEGIN")) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String line : normalized.split("\\R")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
|
||||||
|
if (!trimmed.startsWith("-----")) {
|
||||||
|
builder.append(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static String trim(String s){
|
private static String trim(String s){
|
||||||
return s == null ? "" : s.trim();
|
return s == null ? "" : s.trim();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,343 @@
|
|||||||
package com.cisd.tms.modules.log.service;
|
package com.cisd.tms.modules.log.service;
|
||||||
|
|
||||||
|
import com.cisd.tms.common.enums.ErrorCode;
|
||||||
|
import com.cisd.tms.common.exception.BizException;
|
||||||
|
import org.bouncycastle.asn1.*;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.crypto.digests.SM3Digest;
|
||||||
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
|
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||||
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
class OperationAuditServiceTest {
|
class OperationAuditServiceTest {
|
||||||
|
|
||||||
|
private static final String PROVIDER = "BC";
|
||||||
|
private static final String SM2_CURVE = "sm2p256v1";
|
||||||
|
|
||||||
|
private static final String SIGNATURE_ALGORITHM = "SM3withSM2";
|
||||||
|
private static final int GM0018_ECCREF_PUBLIC_KEY_LENGTH = 132;
|
||||||
|
private static final int GM0018_ECCREF_COMPONENT_LENGTH = 64;
|
||||||
|
private static final int SM2_COORDINATE_LENGTH = 32;
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (Security.getProvider(PROVIDER) == null) {
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String data = "f9e12235524747d7be779981494d5267_AUDIT_ADMIN_FULL_AUTH_LOGIN_172.16.18.214_SUCCESS_UKey 登录 [role=AUDIT_ADMIN, principal=uid=1]__PENDING_null_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwG9NAMsrKVjm0oyYyDtDkJT8OPR03EuFTn+8cbWJevQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHJByEqhbLOINbNXQgOdGEUibWV+1Y3vTNX6SMY9+X9k=_2026-05-21 09:48:58";
|
||||||
|
String sign = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLBYDnGWXQ1kILRDJeEx6qsQqyztWY7UNnwBfGss72UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmzc5T5LzKi/FfAT477Mt8gJM1tL3YUY3aqePWeWfSIc=";
|
||||||
|
String publickey = "AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+ldxsRgzy7LoNRPAmkZFOPQyqm+oqCm+EMmgCifceoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANU1uz7nhgyo7vgyeCZJlY+eeH/vKRMWFFUGGdkD2xOQ";
|
||||||
|
|
||||||
|
verifyLoginSignature(publickey, data, sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void verifyLoginSignature(String pubKey, String loginSignData, String loginSign) {
|
||||||
|
try {
|
||||||
|
PublicKey publicKey = parsePublicKey(pubKey);
|
||||||
|
|
||||||
|
if (loginSignData == null || loginSignData.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("login sign data不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signatureBytes = decodeSignature(loginSign);
|
||||||
|
|
||||||
|
// String signData = sm3Hex(loginSignData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
||||||
|
signature.initVerify(publicKey);
|
||||||
|
|
||||||
|
|
||||||
|
signature.update(loginSignData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
if (!signature.verify(signatureBytes)) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"login signature verification 执行失败"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
System.out.println("true");
|
||||||
|
}
|
||||||
|
} catch (BizException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), ex.getMessage());
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"login signature verification 执行失败"
|
||||||
|
);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BizException(
|
||||||
|
ErrorCode.UNAUTHORIZED.getCode(),
|
||||||
|
"login signature verification 执行失败"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static String sm3Hex(byte[] data) {
|
||||||
|
byte[] digest = sm3(data);
|
||||||
|
return Hex.toHexString(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey parsePublicKey(String value) throws Exception {
|
||||||
|
String trimmed = value == null ? "" : value.trim();
|
||||||
|
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("登录公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.contains("BEGIN")) {
|
||||||
|
return parsePemPublicKey(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = normalize(trimmed);
|
||||||
|
byte[] decoded = decodeHexOrBase64(normalized, "登录公钥无效");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return KeyFactory
|
||||||
|
.getInstance("EC", PROVIDER)
|
||||||
|
.generatePublic(new X509EncodedKeySpec(decoded));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return parseUncompressedPointPublicKey(toPointBytes(decoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey parsePemPublicKey(String pemContent) throws Exception {
|
||||||
|
try (PEMParser parser = new PEMParser(new StringReader(pemContent))) {
|
||||||
|
Object parsed = parser.readObject();
|
||||||
|
|
||||||
|
if (!(parsed instanceof SubjectPublicKeyInfo publicKeyInfo)) {
|
||||||
|
throw new IllegalArgumentException("登录公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JcaPEMKeyConverter()
|
||||||
|
.setProvider(PROVIDER)
|
||||||
|
.getPublicKey(publicKeyInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey parseUncompressedPointPublicKey(byte[] pointBytes) throws Exception {
|
||||||
|
if (pointBytes.length == 64) {
|
||||||
|
byte[] rawPointBytes = pointBytes;
|
||||||
|
pointBytes = new byte[65];
|
||||||
|
pointBytes[0] = 0x04;
|
||||||
|
System.arraycopy(rawPointBytes, 0, pointBytes, 1, rawPointBytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointBytes.length != 65 || pointBytes[0] != 0x04) {
|
||||||
|
throw new IllegalArgumentException("登录公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
ECParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec(SM2_CURVE);
|
||||||
|
|
||||||
|
ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(
|
||||||
|
parameterSpec.getCurve().decodePoint(pointBytes),
|
||||||
|
parameterSpec
|
||||||
|
);
|
||||||
|
|
||||||
|
return KeyFactory
|
||||||
|
.getInstance("EC", PROVIDER)
|
||||||
|
.generatePublic(publicKeySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] sm3(byte[] data) {
|
||||||
|
SM3Digest digest = new SM3Digest();
|
||||||
|
digest.update(data, 0, data.length);
|
||||||
|
|
||||||
|
byte[] result = new byte[digest.getDigestSize()];
|
||||||
|
digest.doFinal(result, 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] decodeSignature(String value) {
|
||||||
|
if (value == null || value.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("login signature不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] decoded = decodeHexOrBase64(normalize(value), "登录签名无效");
|
||||||
|
|
||||||
|
if (isDerSignature(decoded)) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.length == GM0018_ECCREF_COMPONENT_LENGTH * 2) {
|
||||||
|
return rawRsToDer(gm0018SignatureToRawRs(decoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.length == 64) {
|
||||||
|
return rawRsToDer(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("登录签名无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDerSignature(byte[] signatureBytes) {
|
||||||
|
try (ASN1InputStream inputStream = new ASN1InputStream(signatureBytes)) {
|
||||||
|
ASN1Primitive primitive = inputStream.readObject();
|
||||||
|
|
||||||
|
if (!(primitive instanceof ASN1Sequence sequence) || sequence.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence.getObjectAt(0) instanceof ASN1Integer
|
||||||
|
&& sequence.getObjectAt(1) instanceof ASN1Integer;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] rawRsToDer(byte[] rawSignature) {
|
||||||
|
if (rawSignature.length != 64) {
|
||||||
|
throw new IllegalArgumentException("登录签名无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] r = Arrays.copyOfRange(rawSignature, 0, 32);
|
||||||
|
byte[] s = Arrays.copyOfRange(rawSignature, 32, 64);
|
||||||
|
|
||||||
|
ASN1EncodableVector vector = new ASN1EncodableVector();
|
||||||
|
vector.add(new ASN1Integer(new BigInteger(1, r)));
|
||||||
|
vector.add(new ASN1Integer(new BigInteger(1, s)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new DERSequence(vector).getEncoded(ASN1Encoding.DER);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException("登录签名无效", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] toPointBytes(byte[] keyBytes) {
|
||||||
|
if (keyBytes.length == GM0018_ECCREF_PUBLIC_KEY_LENGTH) {
|
||||||
|
return gm0018EccRefToPointBytes(keyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] gm0018EccRefToPointBytes(byte[] keyBytes) {
|
||||||
|
int bitsLittleEndian = (keyBytes[0] & 0xff)
|
||||||
|
| ((keyBytes[1] & 0xff) << 8)
|
||||||
|
| ((keyBytes[2] & 0xff) << 16)
|
||||||
|
| ((keyBytes[3] & 0xff) << 24);
|
||||||
|
|
||||||
|
int bitsBigEndian = ((keyBytes[0] & 0xff) << 24)
|
||||||
|
| ((keyBytes[1] & 0xff) << 16)
|
||||||
|
| ((keyBytes[2] & 0xff) << 8)
|
||||||
|
| (keyBytes[3] & 0xff);
|
||||||
|
|
||||||
|
if (bitsLittleEndian != 256 && bitsBigEndian != 256) {
|
||||||
|
throw new IllegalArgumentException("登录公钥无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pointBytes = new byte[65];
|
||||||
|
pointBytes[0] = 0x04;
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
keyBytes,
|
||||||
|
4 + GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||||
|
pointBytes,
|
||||||
|
1,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
keyBytes,
|
||||||
|
4 + GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||||
|
pointBytes,
|
||||||
|
1 + SM2_COORDINATE_LENGTH,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
return pointBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] gm0018SignatureToRawRs(byte[] signatureBytes) {
|
||||||
|
byte[] rawSignature = new byte[64];
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
signatureBytes,
|
||||||
|
GM0018_ECCREF_COMPONENT_LENGTH - SM2_COORDINATE_LENGTH,
|
||||||
|
rawSignature,
|
||||||
|
0,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
System.arraycopy(
|
||||||
|
signatureBytes,
|
||||||
|
GM0018_ECCREF_COMPONENT_LENGTH * 2 - SM2_COORDINATE_LENGTH,
|
||||||
|
rawSignature,
|
||||||
|
SM2_COORDINATE_LENGTH,
|
||||||
|
SM2_COORDINATE_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
|
return rawSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] decodeHexOrBase64(String value, String message) {
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.matches("(?i)^[0-9a-f]+$") && value.length() % 2 == 0) {
|
||||||
|
return Hex.decode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Base64.getDecoder().decode(value);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new IllegalArgumentException(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalize(String value) {
|
||||||
|
String normalized = value == null ? "" : value.trim();
|
||||||
|
|
||||||
|
if (!normalized.contains("BEGIN")) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String line : normalized.split("\\R")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
|
||||||
|
if (!trimmed.startsWith("-----")) {
|
||||||
|
builder.append(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bytesToHex(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02x", b & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user