实现有差异
This commit is contained in:
parent
38909af664
commit
133363ef18
@ -15,6 +15,7 @@ public interface PcieNativeLibrary extends Library {
|
|||||||
|
|
||||||
int SDF_CloseSession(Pointer hSessionHandle);
|
int SDF_CloseSession(Pointer hSessionHandle);
|
||||||
|
|
||||||
|
|
||||||
int SDF_GetPrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex, byte[] pucPassword, int uiPwdLength);
|
int SDF_GetPrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex, byte[] pucPassword, int uiPwdLength);
|
||||||
|
|
||||||
int SDF_ReleasePrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex);
|
int SDF_ReleasePrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex);
|
||||||
|
|||||||
@ -58,7 +58,8 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AuthServiceImpl implements AuthService {
|
public class
|
||||||
|
AuthServiceImpl implements AuthService {
|
||||||
|
|
||||||
private static final int MAX_FAILED_ATTEMPTS = 5;
|
private static final int MAX_FAILED_ATTEMPTS = 5;
|
||||||
private static final int IDLE_TIMEOUT_MINUTES = 10;
|
private static final int IDLE_TIMEOUT_MINUTES = 10;
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.cisd.tms.modules.openapi.service;
|
||||||
|
|
||||||
|
public interface IOpenApiService {
|
||||||
|
public String rawSign(byte[] origBytes, String dn);
|
||||||
|
public boolean rawVerify(byte[] origBytes, String certStr, String dn);
|
||||||
|
public String dettachedSign(byte[] origBytes, String dn);
|
||||||
|
public String dettachedVerify(byte[] origBytes, String certStr);
|
||||||
|
public String dettachedVerifySimple(byte[] origBytes, String certStr);
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package com.cisd.tms.modules.openapi.service.dto;
|
||||||
|
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用证书类
|
||||||
|
*/
|
||||||
|
public class GenericCertificate {
|
||||||
|
|
||||||
|
private X509Certificate certificate;
|
||||||
|
private String dn;
|
||||||
|
private String issuer;
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
public GenericCertificate(X509Certificate certificate) {
|
||||||
|
this.certificate = certificate;
|
||||||
|
if (certificate != null) {
|
||||||
|
this.dn = certificate.getSubjectDN().getName();
|
||||||
|
this.issuer = certificate.getIssuerDN().getName();
|
||||||
|
this.serialNumber = certificate.getSerialNumber().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate getCertificate() {
|
||||||
|
return certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificate(X509Certificate certificate) {
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDn() {
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDn(String dn) {
|
||||||
|
this.dn = dn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuer() {
|
||||||
|
return issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
this.issuer = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerialNumber() {
|
||||||
|
return serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSerialNumber(String serialNumber) {
|
||||||
|
this.serialNumber = serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "GenericCertificate{" +
|
||||||
|
"dn='" + dn + '\'' +
|
||||||
|
", issuer='" + issuer + '\'' +
|
||||||
|
", serialNumber='" + serialNumber + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,351 @@
|
|||||||
|
package com.cisd.tms.modules.openapi.service.impl;
|
||||||
|
|
||||||
|
import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService;
|
||||||
|
import com.cisd.tms.modules.openapi.service.IOpenApiService;
|
||||||
|
import com.cisd.tms.modules.openapi.service.dto.GenericCertificate;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||||
|
import org.bouncycastle.cms.CMSSignedData;
|
||||||
|
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||||
|
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||||
|
import org.bouncycastle.util.encoders.Base64;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OpenApiService implements IOpenApiService {
|
||||||
|
|
||||||
|
private JnaPcieCryptoService sdf;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String rawSign(byte[] origBytes, String dn) {
|
||||||
|
try {
|
||||||
|
// 使用固定的密钥索引 0
|
||||||
|
int keyIndex = 0;
|
||||||
|
|
||||||
|
// 获取证书和公钥
|
||||||
|
X509Certificate cert = certManager.getCertificate(dn);
|
||||||
|
if (cert == null) {
|
||||||
|
throw new RuntimeException("Certificate not found for DN: " + dn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取公钥字节数组
|
||||||
|
byte[] publicKey = cert.getPublicKey().getEncoded();
|
||||||
|
|
||||||
|
// 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID)
|
||||||
|
byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节
|
||||||
|
IntByReference hashLength = new IntByReference();
|
||||||
|
|
||||||
|
// 使用空指针作为会话句柄(SDFSoft 支持)
|
||||||
|
Pointer sessionHandle = Pointer.NULL;
|
||||||
|
|
||||||
|
// 默认的 SM2 用户 ID
|
||||||
|
String userId = "1234567812345678";
|
||||||
|
|
||||||
|
// 初始化哈希(包含公钥和用户 ID)
|
||||||
|
int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length());
|
||||||
|
if (ret != 0) {
|
||||||
|
throw new RuntimeException("SDF_HashInit failed with code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新哈希(添加原始数据)
|
||||||
|
ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length);
|
||||||
|
if (ret != 0) {
|
||||||
|
throw new RuntimeException("SDF_HashUpdate failed with code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成哈希
|
||||||
|
ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength);
|
||||||
|
if (ret != 0) {
|
||||||
|
throw new RuntimeException("SDF_HashFinal failed with code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 SDF 接口进行 SM2 签名
|
||||||
|
byte[] signature = new byte[64]; // SM2 签名长度为 64 字节
|
||||||
|
ret = sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature);
|
||||||
|
if (ret != 0) {
|
||||||
|
throw new RuntimeException("SDF_InternalSign_ECC failed with code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回 Base64 编码的签名
|
||||||
|
return new String(Base64.encode(signature));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("Raw sign failed: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean rawVerify(byte[] origBytes, String certStr, String dn) {
|
||||||
|
try {
|
||||||
|
// 获取证书
|
||||||
|
X509Certificate cert = certManager.getCertificate(dn);
|
||||||
|
if (cert == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码签名
|
||||||
|
byte[] signature = Base64.decode(certStr);
|
||||||
|
|
||||||
|
// 获取公钥并转换为 X.509 编码格式
|
||||||
|
byte[] publicKey = cert.getPublicKey().getEncoded();
|
||||||
|
|
||||||
|
// 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID)
|
||||||
|
byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节
|
||||||
|
IntByReference hashLength = new IntByReference();
|
||||||
|
|
||||||
|
// 使用空指针作为会话句柄(SDFSoft 支持)
|
||||||
|
Pointer sessionHandle = Pointer.NULL;
|
||||||
|
|
||||||
|
// 默认的 SM2 用户 ID(与签名时保持一致)
|
||||||
|
String userId = "1234567812345678";
|
||||||
|
|
||||||
|
// 初始化哈希(包含公钥和用户 ID)
|
||||||
|
int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length());
|
||||||
|
if (ret != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新哈希(添加原始数据)
|
||||||
|
ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length);
|
||||||
|
if (ret != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成哈希
|
||||||
|
ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength);
|
||||||
|
if (ret != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 SDF 接口进行 SM2 验签
|
||||||
|
ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, publicKey, hash, hashLength.getValue(), signature);
|
||||||
|
return ret == 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dettachedSign(byte[] origBytes, String dn) {
|
||||||
|
try {
|
||||||
|
// 获取密钥对和证书
|
||||||
|
KeyPair keyPair = certManager.getKeyPair(dn);
|
||||||
|
X509Certificate cert = certManager.getCertificate(dn);
|
||||||
|
if (keyPair == null || cert == null) {
|
||||||
|
throw new RuntimeException("Certificate not found for DN: " + dn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 PKCS#7 签名
|
||||||
|
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||||
|
generator.addSignerInfoGenerator(
|
||||||
|
new JcaSignerInfoGeneratorBuilder(
|
||||||
|
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
|
||||||
|
.build(
|
||||||
|
new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("SHA256withECDSA")
|
||||||
|
.setProvider("BC")
|
||||||
|
.build(keyPair.getPrivate()),
|
||||||
|
|
||||||
|
cert)
|
||||||
|
);
|
||||||
|
generator.addCertificate(new org.bouncycastle.cert.jcajce.JcaX509CertificateHolder(cert));
|
||||||
|
|
||||||
|
CMSSignedData signedData = generator.generate(new CMSProcessableByteArray(origBytes), false);
|
||||||
|
|
||||||
|
// 返回 Base64 编码的签名
|
||||||
|
return new String(Base64.encode(signedData.getEncoded()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException("Detached sign failed: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dettachedVerify(byte[] origBytes, String certStr) {
|
||||||
|
try {
|
||||||
|
// 解码签名
|
||||||
|
byte[] signedDataBytes = Base64.decode(certStr);
|
||||||
|
|
||||||
|
// 使用原始数据验证签名
|
||||||
|
CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes);
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) {
|
||||||
|
org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj;
|
||||||
|
|
||||||
|
// 获取证书
|
||||||
|
java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID());
|
||||||
|
org.bouncycastle.cert.X509CertificateHolder certHolder =
|
||||||
|
(org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next();
|
||||||
|
|
||||||
|
X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter()
|
||||||
|
.setProvider("BC")
|
||||||
|
.getCertificate(certHolder);
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
try {
|
||||||
|
if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) {
|
||||||
|
// 验证证书有效性(检查有效期)
|
||||||
|
cert.checkValidity(new java.util.Date());
|
||||||
|
|
||||||
|
return new GenericCertificate(cert);
|
||||||
|
}
|
||||||
|
} catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) {
|
||||||
|
// 签名验证失败(数据不匹配),不打印堆栈信息
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dettachedVerifySimple(byte[] origBytes, String certStr) {
|
||||||
|
try {
|
||||||
|
// 解码签名
|
||||||
|
byte[] signedDataBytes = Base64.decode(certStr);
|
||||||
|
|
||||||
|
// 使用原始数据验证签名
|
||||||
|
CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes);
|
||||||
|
|
||||||
|
// 验证签名(不验证证书有效性)
|
||||||
|
for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) {
|
||||||
|
org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj;
|
||||||
|
|
||||||
|
// 获取证书
|
||||||
|
java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID());
|
||||||
|
org.bouncycastle.cert.X509CertificateHolder certHolder =
|
||||||
|
(org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next();
|
||||||
|
|
||||||
|
X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter()
|
||||||
|
.setProvider("BC")
|
||||||
|
.getCertificate(certHolder);
|
||||||
|
|
||||||
|
// 只验证签名是否正确,不检查证书有效期
|
||||||
|
try {
|
||||||
|
if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) {
|
||||||
|
return new GenericCertificate(cert);
|
||||||
|
}
|
||||||
|
} catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) {
|
||||||
|
// 签名验证失败(数据不匹配),不打印堆栈信息
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ASN.1 DER 编码的签名转换为 r||s 格式
|
||||||
|
*/
|
||||||
|
private byte[] derToRs(byte[] derSignature) {
|
||||||
|
int offset = 2; // 跳过 0x30 和长度字节
|
||||||
|
|
||||||
|
// 读取 r
|
||||||
|
int rLength = derSignature[offset + 1] & 0xFF;
|
||||||
|
byte[] r = new byte[32];
|
||||||
|
int rStart = offset + 2;
|
||||||
|
int rCopyLength = Math.min(rLength, 32);
|
||||||
|
if (rLength <= 32) {
|
||||||
|
System.arraycopy(derSignature, rStart, r, 32 - rLength, rCopyLength);
|
||||||
|
} else {
|
||||||
|
System.arraycopy(derSignature, rStart + (rLength - 32), r, 0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 s
|
||||||
|
offset += 2 + rLength;
|
||||||
|
int sLength = derSignature[offset + 1] & 0xFF;
|
||||||
|
byte[] s = new byte[32];
|
||||||
|
int sStart = offset + 2;
|
||||||
|
int sCopyLength = Math.min(sLength, 32);
|
||||||
|
if (sLength <= 32) {
|
||||||
|
System.arraycopy(derSignature, sStart, s, 32 - sLength, sCopyLength);
|
||||||
|
} else {
|
||||||
|
System.arraycopy(derSignature, sStart + (sLength - 32), s, 0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组合 r||s
|
||||||
|
byte[] result = new byte[64];
|
||||||
|
System.arraycopy(r, 0, result, 0, 32);
|
||||||
|
System.arraycopy(s, 0, result, 32, 32);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 r||s 格式的签名转换为 ASN.1 DER 编码
|
||||||
|
*/
|
||||||
|
private byte[] rsToDer(byte[] rsSignature) {
|
||||||
|
if (rsSignature.length < 64) {
|
||||||
|
return rsSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] r = new byte[32];
|
||||||
|
byte[] s = new byte[32];
|
||||||
|
System.arraycopy(rsSignature, 0, r, 0, 32);
|
||||||
|
System.arraycopy(rsSignature, 32, s, 0, 32);
|
||||||
|
|
||||||
|
r = stripLeadingZeros(r);
|
||||||
|
s = stripLeadingZeros(s);
|
||||||
|
|
||||||
|
if ((r[0] & 0x80) != 0) {
|
||||||
|
byte[] temp = new byte[r.length + 1];
|
||||||
|
System.arraycopy(r, 0, temp, 1, r.length);
|
||||||
|
r = temp;
|
||||||
|
}
|
||||||
|
if ((s[0] & 0x80) != 0) {
|
||||||
|
byte[] temp = new byte[s.length + 1];
|
||||||
|
System.arraycopy(s, 0, temp, 1, s.length);
|
||||||
|
s = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalLength = 2 + 2 + r.length + 2 + s.length;
|
||||||
|
byte[] der = new byte[totalLength];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
der[offset++] = 0x30;
|
||||||
|
der[offset++] = (byte) (totalLength - 2);
|
||||||
|
|
||||||
|
der[offset++] = 0x02;
|
||||||
|
der[offset++] = (byte) r.length;
|
||||||
|
System.arraycopy(r, 0, der, offset, r.length);
|
||||||
|
offset += r.length;
|
||||||
|
|
||||||
|
der[offset++] = 0x02;
|
||||||
|
der[offset++] = (byte) s.length;
|
||||||
|
System.arraycopy(s, 0, der, offset, s.length);
|
||||||
|
|
||||||
|
return der;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去除字节数组前面的零字节
|
||||||
|
*/
|
||||||
|
private byte[] stripLeadingZeros(byte[] data) {
|
||||||
|
int start = 0;
|
||||||
|
while (start < data.length && data[start] == 0) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
if (start == data.length) {
|
||||||
|
return new byte[]{0};
|
||||||
|
}
|
||||||
|
byte[] result = new byte[data.length - start];
|
||||||
|
System.arraycopy(data, start, result, 0, result.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
||||||
public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
public class OpenApiSignAuthInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
private static final String APP_ID_HEADER = "X-App-Id";
|
private static final String APP_ID_HEADER = "X-App-Id";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user