diff --git a/build.gradle b/build.gradle index 0746d0c2dbc659aeaaee64c3ec0a20d5633a447e..ef0e93da596d85c58a1e38db49329dd654642eff 100644 --- a/build.gradle +++ b/build.gradle @@ -23,12 +23,17 @@ tasks.withType(Copy) { } dependencies { - implementation 'org.bdware.doip:doip-sdk:1.3.4' + implementation 'org.bdware.doip:doip-sdk:1.3.5' + implementation 'org.bdware.doip:doip-encrypt-tool:0.1.0' implementation 'org.apache.logging.log4j:log4j-core:2.17.2' implementation 'org.apache.logging.log4j:log4j-api:2.17.2' implementation 'io.netty:netty-handler:4.1.77.Final' implementation 'org.apache.jmeter:ApacheJMeter_java:5.0' testImplementation 'junit:junit:4.12' + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.69' + implementation 'com.nimbusds:nimbus-jose-jwt:9.10' + implementation 'io.netty:netty-all:4.1.77.Final' } task copyJar(type: Copy) { diff --git a/config/clientConf.json b/config/clientConf.json index 68d3890f4a27f2f2bceda8ede1c3faaee931f600..cef1f30b3112218f69c25ee28159b424d1599e0c 100644 --- a/config/clientConf.json +++ b/config/clientConf.json @@ -1,4 +1,4 @@ { - "serviceID" : "localTest/doip.tcp", - "serviceAddr": "tcp://127.0.0.1:8001" + "serviceID" : "local/compatibility", + "serviceAddr": "tcp://127.0.0.1:8003" } \ No newline at end of file diff --git a/config/serverConf.json b/config/serverConf.json index 9489ee669d163980539fc98c7edd1aec9d5a4688..5bb5ead392181eca2cca55b8ecbf072baa2f81df 100644 --- a/config/serverConf.json +++ b/config/serverConf.json @@ -1,15 +1,15 @@ { - "id": "localTest/doip.tcp", + "id": "local/compatibility", "serviceDescription": "testTCPServer", "publicKey": "{\"kty\":\"EC\",\"d\":\"VPvAXurYhEwCRbIuSCEPOaTyfUIbH6an4scA4BpdWCw\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"86.5000.470\\/dou.TEST\",\"x\":\"IFVGcQ22vd7SEd1HsjcYuaLWUrfj4ochceom6YNCX4g\",\"y\":\"HSuB60fA_53vi4L30WiVQjouvAB0gSPAS8kf8Ny3RN0\"}", "serviceName": "testTCPServer", "listenerInfos": [ { - "url": "tcp://127.0.0.1:8001", + "url": "tcp://127.0.0.1:8003", "protocolVersion": "2.1" }, { - "url": "udp://127.0.0.1:8002", + "url": "udp://127.0.0.1:8004", "protocolVersion": "2.1" } ], diff --git a/src/main/encrypt/org/bdware/doip/encrypt/EncryptConstants.java b/src/main/encrypt/org/bdware/doip/encrypt/EncryptConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..453a7e3fd345fffdbabb4d4ca95bd5124c874116 --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/EncryptConstants.java @@ -0,0 +1,12 @@ +package org.bdware.doip.encrypt; + + +import org.bdware.irp.irplib.util.EncoderUtils; + +public class EncryptConstants { + public static final byte[] CREDENTIAL_SIGNEDINFO_TYPE_SM2 = EncoderUtils.encodeString("HS_SIGNED_SM2"); + public static final byte[] CREDENTIAL_DIGEST_ALG_SM2 = EncoderUtils.encodeString("JWK"); + + public static final byte[] CREDENTIAL_SIGNEDINFO_TYPE_RSA = EncoderUtils.encodeString("HS_SIGNED_RSA"); + +} \ No newline at end of file diff --git a/src/main/encrypt/org/bdware/doip/encrypt/GMCryptoManager.java b/src/main/encrypt/org/bdware/doip/encrypt/GMCryptoManager.java new file mode 100644 index 0000000000000000000000000000000000000000..802e0a9a3299be479379157ac6a08618c15718b9 --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/GMCryptoManager.java @@ -0,0 +1,105 @@ +package org.bdware.doip.encrypt; + +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.MessageCredential; +import org.bdware.doip.endpoint.CryptoManager; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; +import org.zz.gmhelper.BCECUtil; +import org.zz.gmhelper.SM2KeyPair; +import org.zz.gmhelper.SM2Util; +import org.zz.gmhelper.SM4Util; + +public class GMCryptoManager implements CryptoManager { + byte[] sm4key; + SM2KeyPair ownKeyPair; + KeyRetriever retriever; + + public GMCryptoManager(SM2KeyPair ownKeyPair, KeyRetriever retriever) { + this.ownKeyPair = ownKeyPair; + this.retriever = retriever; + } + + @Override + public byte[] getSymmetricKey(DoipMessage message) { + if (sm4key == null) sm4key = generateOne(); + return sm4key; + } + + private byte[] generateOne() { + try { + return SM4Util.generateKey(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public SM2KeyPair getPublicKey(DoipMessage message) { + return retriever.retriveKey(message); + } + + @Override + public SM2KeyPair getOwnKeyPair(DoipMessage message) { + return ownKeyPair; + } + + @Override + public byte[] encryptUseSymmetricKey(byte[] encodedData, byte[] symmetricKey) { + try { + return SM4Util.encrypt_ECB_NoPadding(symmetricKey, encodedData); + } catch (Throwable t) { + t.printStackTrace(); + } + return encodedData; + } + + @Override + public String encryptSymmetricKey(byte[] symmetricKey, SM2KeyPair publicKey) { + try { + return ByteUtils.toHexString(SM2Util.encrypt(publicKey.getPublicKey(), symmetricKey)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public byte[] decryptSymmetricKey(String encryptedSymmetricKey, SM2KeyPair mykey) { + try { + return (SM2Util.decrypt(mykey.getPrivateKeyParameter(), ByteUtils.fromHexString(encryptedSymmetricKey))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public byte[] decryptUseSymmetricKey(byte[] encodedData, byte[] symmetricKey) { + try { + return SM4Util.decrypt_ECB_NoPadding(symmetricKey, encodedData); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void putPubKeyToCredential(DoipMessage message, SM2KeyPair publicKey) { + if (message.credential == null) message.credential = new MessageCredential((String) null); + message.credential.setAttributes("clientPublicKey", publicKey.getPublicKeyStr()); + } + + @Override + public SM2KeyPair getPubKeyFromCredential(DoipMessage message) { + try { + String str = message.credential.getAttriburte("clientPublicKey").getAsString(); + ECPublicKeyParameters point = + BCECUtil.createECPublicKeyFromStrParameters( + str, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + SM2KeyPair pair = new SM2KeyPair(point, null); + return pair; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/encrypt/org/bdware/doip/encrypt/JWKCryptoManager.java b/src/main/encrypt/org/bdware/doip/encrypt/JWKCryptoManager.java new file mode 100644 index 0000000000000000000000000000000000000000..61ec0a2a8df3604c979e86b95e13efaec42bac44 --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/JWKCryptoManager.java @@ -0,0 +1,139 @@ +package org.bdware.doip.encrypt; + +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.DirectDecrypter; +import com.nimbusds.jose.crypto.DirectEncrypter; +import com.nimbusds.jose.crypto.RSADecrypter; +import com.nimbusds.jose.crypto.RSAEncrypter; +import com.nimbusds.jose.jwk.JWK; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.MessageCredential; +import org.bdware.doip.endpoint.CryptoManager; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; + +//TODO publickey should retrieve? +public class JWKCryptoManager implements CryptoManager { + SecretKey aes; + JWK ownKeyPair; + KeyRetriever retriever; + + public JWKCryptoManager(JWK ownKeyPair, KeyRetriever retriever) { + this.ownKeyPair = ownKeyPair; + this.retriever = retriever; + } + + @Override + public SecretKey getSymmetricKey(DoipMessage message) { + if (aes == null) aes = generateOne(); + return aes; + } + + private SecretKey generateOne() { + try { + int keyBitLength = EncryptionMethod.A256GCM.cekBitLength(); + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(keyBitLength); + SecretKey key = keyGen.generateKey(); + return key; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + @Override + public JWK getPublicKey(DoipMessage message) { + return retriever.retriveKey(message); + } + + @Override + public JWK getOwnKeyPair(DoipMessage message) { + return ownKeyPair; + } + + @Override + public byte[] encryptUseSymmetricKey(byte[] encodedData, SecretKey symmetricKey) { + try { + JWEHeader header = new JWEHeader(JWEAlgorithm.DIR, EncryptionMethod.A256GCM); + Payload payload = new Payload(encodedData); + JWEObject jweObject = new JWEObject(header, payload); + jweObject.encrypt(new DirectEncrypter(symmetricKey)); + String jweString = jweObject.serialize(); + return jweString.getBytes(StandardCharsets.UTF_8); + } catch (Throwable t) { + t.printStackTrace(); + } + return encodedData; + } + + @Override + public String encryptSymmetricKey(SecretKey symmetricKey, JWK publicKey) { + JWEHeader header = new JWEHeader( + JWEAlgorithm.RSA_OAEP_256, + EncryptionMethod.A128GCM + ); + byte[] key = symmetricKey.getEncoded(); + JWEObject object = new JWEObject(header, new Payload(key)); + try { + RSAEncrypter encrypter = new RSAEncrypter(publicKey.toRSAKey().toRSAPublicKey()); + object.encrypt(encrypter); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + return object.serialize(); + } + + @Override + public SecretKey decryptSymmetricKey(String encryptedSymmetricKey, JWK mykey) { + try { + JWEObject object = JWEObject.parse(encryptedSymmetricKey); + object.decrypt(new RSADecrypter(mykey.toRSAKey())); + byte[] bytes = object.getPayload().toBytes(); + SecretKey key = new SecretKeySpec(bytes, "AES"); + return key; + } catch (JOSEException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + @Override + public byte[] decryptUseSymmetricKey(byte[] encodedData, SecretKey symmetricKey) { + JWEObject jweObject = null; + try { + jweObject = JWEObject.parse(new String(encodedData, StandardCharsets.UTF_8)); + jweObject.decrypt(new DirectDecrypter(symmetricKey)); + Payload payload = jweObject.getPayload(); + return payload.toBytes(); + } catch (ParseException e) { + throw new RuntimeException(e); + } catch (KeyLengthException e) { + throw new RuntimeException(e); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + } + + @Override + public void putPubKeyToCredential(DoipMessage message, JWK publicKey) { + if (message.credential == null) message.credential = new MessageCredential((String) null); + message.credential.setAttributes("clientPublicKey", publicKey.toPublicJWK().toJSONString()); + } + + @Override + public JWK getPubKeyFromCredential(DoipMessage message) { + try { + String str = message.credential.getAttriburte("clientPublicKey").getAsString(); + return JWK.parse(str); + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/encrypt/org/bdware/doip/encrypt/JWKSigner.java b/src/main/encrypt/org/bdware/doip/encrypt/JWKSigner.java new file mode 100644 index 0000000000000000000000000000000000000000..34d0cc6c683ccfb080afd6dabe151692f8b06645 --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/JWKSigner.java @@ -0,0 +1,86 @@ +package org.bdware.doip.encrypt; + +import com.nimbusds.jose.jwk.JWK; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.DoipMessageSigner; +import org.bdware.irp.deprecated.GlobalUtils; +import org.bdware.irp.irplib.core.IrpMessage; +import org.bdware.irp.irplib.core.IrpMessageSigner; +import org.bdware.irp.irplib.util.EncoderUtils; +import org.bdware.irp.irplib.util.IrpCommon; + +public class JWKSigner implements IrpMessageSigner, DoipMessageSigner { + JWK jwk; + + + public JWKSigner(JWK jwk) { + this.jwk = jwk; + } + + //sign the massage by Signature, need init first + public final boolean verifyMessage(IrpMessage irpMessage) { + try { + //generate the message signature data first + byte[] digestData = irpMessage.getEncodedMessageHeaderBody(); + String signature = EncoderUtils.decodeString(irpMessage.credential.signature); + String pubkey = EncoderUtils.decodeString(irpMessage.credential.signerDoid); + return GlobalUtils.verifySigByJWK(digestData, signature, pubkey); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + //sign the massage by JWK + public static final void signMessage(IrpMessage irpMessage, JWK jwk) { + try { + if (jwk == null) return; + irpMessage.header.setCertifiedFlag(true); + //generate the message signature data first + byte[] digestData = irpMessage.getEncodedMessageHeaderBody(); + //not digest the body and header, sign immediately + String signature = GlobalUtils.signByteArrayByJWK(digestData, jwk); + irpMessage.credential.signerDoid = EncoderUtils.encodeString(jwk.getKeyID()); + irpMessage.credential.signedInfoType = IrpCommon.CREDENTIAL_SIGNEDINFO_TYPE_JWK; + irpMessage.credential.signedInfoDigestAlgorithm = IrpCommon.CREDENTIAL_DIGEST_ALG_JWK; + irpMessage.credential.signature = EncoderUtils.encodeString(signature); + irpMessage.credential.signedInfoLength = irpMessage.credential.signedInfoDigestAlgorithm.length + irpMessage.credential.signature.length; + irpMessage.encodedMessage = null; //update the encode message + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void signMessage(IrpMessage irpMessage) { + signMessage(irpMessage, jwk); + } + + @Override + public boolean verifyMessage(DoipMessage doipMessage) { + try { + byte[] digestData = doipMessage.getDoipMessageHeaderBody(); + String pubkeyStr = doipMessage.credential.attributes.get("signer").getAsString(); + String signature = EncoderUtils.decodeString(doipMessage.credential.getSignature()); + return GlobalUtils.verifySigByJWK(digestData, signature, pubkeyStr); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + @Override + public void signMessage(DoipMessage doipMessage) { + try { + if (jwk == null) return; + doipMessage.header.setIsCertified(true); + //generate the message signature data first + byte[] digestData = doipMessage.getDoipMessageHeaderBody(); + //not digest the body and header, sign immediately + String signature = GlobalUtils.signByteArrayByJWK(digestData, jwk); + doipMessage.credential.setSigner(jwk.getKeyID()); + doipMessage.credential.setSignature(EncoderUtils.encodeString(signature)); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/encrypt/org/bdware/doip/encrypt/KeyRetriever.java b/src/main/encrypt/org/bdware/doip/encrypt/KeyRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..76a9715fe5d87e635ddbd6d87b93b9b028b458b0 --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/KeyRetriever.java @@ -0,0 +1,7 @@ +package org.bdware.doip.encrypt; + +import org.bdware.doip.codec.doipMessage.DoipMessage; + +public interface KeyRetriever { + T retriveKey(DoipMessage message); +} diff --git a/src/main/encrypt/org/bdware/doip/encrypt/SM2Signer.java b/src/main/encrypt/org/bdware/doip/encrypt/SM2Signer.java new file mode 100644 index 0000000000000000000000000000000000000000..bcb1130b8ba3954fbe8329a1d85eec658fc7ac9f --- /dev/null +++ b/src/main/encrypt/org/bdware/doip/encrypt/SM2Signer.java @@ -0,0 +1,117 @@ +package org.bdware.doip.encrypt; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.DoipMessageSigner; +import org.bdware.doip.codec.doipMessage.MessageCredential; +import org.bdware.irp.irplib.core.IrpMessage; +import org.bdware.irp.irplib.core.IrpMessageSigner; +import org.bdware.irp.irplib.util.EncoderUtils; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; +import org.zz.gmhelper.BCECUtil; +import org.zz.gmhelper.SM2KeyPair; +import org.zz.gmhelper.SM2Util; + +import java.nio.charset.StandardCharsets; + +//This class is lower than IrpHandler +//Only resolve operation can be anonymous +public class SM2Signer implements IrpMessageSigner, DoipMessageSigner { + static Logger LOGGER = LogManager.getLogger(SM2Signer.class); + SM2KeyPair keyPair; + boolean enableSign; + boolean enableVerify; + + public SM2Signer(SM2KeyPair keyPair) { + this(keyPair, keyPair != null, keyPair != null); + } + + public SM2Signer(SM2KeyPair keyPair, boolean enableSign, boolean enableVerify) { + this.keyPair = keyPair; + this.enableSign = enableSign; + this.enableVerify = enableVerify; + } + + public SM2KeyPair getKeyPair() { + return keyPair; + } + + @Override + public void signMessage(IrpMessage msg) { + if (!enableSign) return; + try { + //TODO sign here + msg.header.setCertifiedFlag(true); + byte[] digestData = msg.getEncodedMessageHeaderBody(); + byte[] signature = SM2Util.sign(keyPair.getPrivateKeyParameter(), digestData); + msg.credential.signerDoid = keyPair.getPublicKeyStr().getBytes(StandardCharsets.UTF_8); + msg.credential.signedInfoType = EncryptConstants.CREDENTIAL_SIGNEDINFO_TYPE_SM2; + msg.credential.signedInfoDigestAlgorithm = EncryptConstants.CREDENTIAL_DIGEST_ALG_SM2; + msg.credential.signature = signature; + msg.credential.signedInfoLength = msg.credential.signedInfoDigestAlgorithm.length + msg.credential.signature.length; + msg.encodedMessage = null; //update the encode message + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @Override + public boolean verifyMessage(IrpMessage msg) { + if (!enableVerify) return true; + try { + byte[] digestData = msg.getEncodedMessageHeaderBody(); + byte[] signature = msg.credential.signature; + String pubkeyStr = EncoderUtils.decodeString(msg.credential.signerDoid); + LOGGER.error("verify:" + prettyBytes(digestData) + " sign:" + prettyBytes(signature) + " pubKey:" + pubkeyStr); + ECPublicKeyParameters pubkey = BCECUtil.createECPublicKeyFromStrParameters( + pubkeyStr, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + boolean result = SM2Util.verify(pubkey, digestData, signature); + // LOGGER.debug("verify!" + result); + return result; + } catch (Exception e) { + LOGGER.debug(e); + } + // LOGGER.debug("verify! failed"); + return false; + } + + @Override + public void signMessage(DoipMessage msg) { + if (!enableSign) return; + try { + msg.header.setIsCertified(true); + byte[] digestData = msg.getDoipMessageHeaderBody(); + byte[] signature = SM2Util.sign(keyPair.getPrivateKeyParameter(), digestData); + if (msg.credential == null) msg.credential = new MessageCredential((String) null); + msg.credential.setSigner(keyPair.getPublicKeyStr()); + msg.credential.setSignature(signature); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private String prettyBytes(byte[] digestData) { + return ByteUtils.toHexString(digestData); + } + + + @Override + public boolean verifyMessage(DoipMessage msg) { + if (!enableVerify) return true; + try { + String pubkeyStr = msg.credential.getSigner(); + byte[] digestData = msg.getDoipMessageHeaderBody(); + byte[] signature = msg.credential.getSignature(); + ECPublicKeyParameters pubkey = BCECUtil.createECPublicKeyFromStrParameters( + pubkeyStr, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + boolean result = SM2Util.verify(pubkey, digestData, signature); + return result; + } catch (Exception e) { + } + return false; + } + +} diff --git a/src/main/gm/org.zz/gmhelper/BCECUtil.java b/src/main/gm/org.zz/gmhelper/BCECUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..47f2d1b886fa61cc3785471112545cde09565aea --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/BCECUtil.java @@ -0,0 +1,517 @@ +package org.zz.gmhelper; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ECPoint; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.*; +import java.math.BigInteger; +import java.security.*; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * 这个工具类的方法,也适用于其他基于BC库的ECC算法 + */ +public class BCECUtil { + public static final String ALGO_NAME_EC = "EC"; + public static final String PEM_STRING_PUBLIC = "PUBLIC KEY"; + public static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY"; + + /** + * 生成ECC密钥对 + * + * @param domainParameters domain参数 + * @param random 随机生成器 + * @return ECC密钥对 + */ + public static AsymmetricCipherKeyPair generateKeyPairParameter( + ECDomainParameters domainParameters, SecureRandom random) { + ECKeyGenerationParameters keyGenerationParams = + new ECKeyGenerationParameters(domainParameters, random); + ECKeyPairGenerator keyGen = new ECKeyPairGenerator(); + keyGen.init(keyGenerationParams); + return keyGen.generateKeyPair(); + } + + public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) + throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME); + ECParameterSpec parameterSpec = + new ECParameterSpec( + domainParameters.getCurve(), + domainParameters.getG(), + domainParameters.getN(), + domainParameters.getH()); + kpg.initialize(parameterSpec, (null == random ? new SecureRandom() : random)); + return kpg.generateKeyPair(); + } + + public static int getCurveLength(ECKeyParameters ecKey) { + return getCurveLength(ecKey.getParameters()); + } + + public static int getCurveLength(ECDomainParameters domainParams) { + return (domainParams.getCurve().getFieldSize() + 7) / 8; + } + + public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) { + if (src.length == curveLength) { + return src; + } + + byte[] result = new byte[curveLength]; + if (src.length > curveLength) { + System.arraycopy(src, src.length - result.length, result, 0, result.length); + } else { + System.arraycopy(src, 0, result, result.length - src.length, src.length); + } + return result; + } + + /** + * @param dHex 十六进制字符串形式的私钥d值,如果是SM2算法,Hex字符串长度应该是64(即32字节) + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC私钥 + */ + public static ECPrivateKeyParameters createECPrivateKeyParameters( + String dHex, ECDomainParameters domainParameters) { + return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters); + } + + /** + * @param dBytes 字节数组形式的私钥d值,如果是SM2算法,应该是32字节 + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC私钥 + */ + public static ECPrivateKeyParameters createECPrivateKeyParameters( + byte[] dBytes, ECDomainParameters domainParameters) { + return createECPrivateKeyParameters(new BigInteger(1, dBytes), domainParameters); + } + + /** + * @param d 大数形式的私钥d值 + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC私钥 + */ + public static ECPrivateKeyParameters createECPrivateKeyParameters( + BigInteger d, ECDomainParameters domainParameters) { + return new ECPrivateKeyParameters(d, domainParameters); + } + + /** + * 根据EC私钥构造EC公钥 + * + * @param priKey ECC私钥参数对象 + * @return EC公钥 + */ + public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) { + ECDomainParameters domainParameters = priKey.getParameters(); + ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD()); + return new ECPublicKeyParameters(q, domainParameters); + } + + /** + * @param x 大数形式的公钥x分量 + * @param y 大数形式的公钥y分量 + * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE} + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC公钥 + */ + public static ECPublicKeyParameters createECPublicKeyParameters( + BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) { + return createECPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters); + } + + /** + * @param xHex 十六进制形式的公钥x分量,如果是SM2算法,Hex字符串长度应该是64(即32字节) + * @param yHex 十六进制形式的公钥y分量,如果是SM2算法,Hex字符串长度应该是64(即32字节) + * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE} + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC公钥 + */ + public static ECPublicKeyParameters createECPublicKeyParameters( + String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) { + return createECPublicKeyParameters( + ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex), curve, domainParameters); + } + + /** + * @param xBytes 十六进制形式的公钥x分量,如果是SM2算法,应该是32字节 + * @param yBytes 十六进制形式的公钥y分量,如果是SM2算法,应该是32字节 + * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE} + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC公钥 + */ + public static ECPublicKeyParameters createECPublicKeyParameters( + byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) { + final byte uncompressedFlag = 0x04; + int curveLength = getCurveLength(domainParameters); + xBytes = fixToCurveLengthBytes(curveLength, xBytes); + yBytes = fixToCurveLengthBytes(curveLength, yBytes); + byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length]; + encodedPubKey[0] = uncompressedFlag; + System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length); + System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length); + return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters); + } + + /** + * @param str 十六进制形式的公钥x分量,如果是SM2算法,应该是32字节 + * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE} + * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS} + * @return EC公钥 + */ + + public static ECPublicKeyParameters createECPublicKeyFromStrParameters( + String str, ECCurve curve, ECDomainParameters domainParameters) { + return new ECPublicKeyParameters( + curve.decodePoint(ByteUtils.fromHexString(str)), domainParameters); + } + + public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) { + ECParameterSpec parameterSpec = ecPriKey.getParameters(); + ECDomainParameters domainParameters = + new ECDomainParameters( + parameterSpec.getCurve(), + parameterSpec.getG(), + parameterSpec.getN(), + parameterSpec.getH()); + return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters); + } + + public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) { + ECParameterSpec parameterSpec = ecPubKey.getParameters(); + ECDomainParameters domainParameters = + new ECDomainParameters( + parameterSpec.getCurve(), + parameterSpec.getG(), + parameterSpec.getN(), + parameterSpec.getH()); + return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters); + } + + public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo( + SubjectPublicKeyInfo subPubInfo) + throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, + IOException { + return BCECUtil.convertX509ToECPublicKey( + subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.DER)); + } + + /** + * 将ECC私钥转换为PKCS8标准的字节流 + * + * @param priKey 私钥 + * @param pubKey 可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了 + * @return 字节数组 + */ + public static byte[] convertECPrivateKeyToPKCS8( + ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) { + ECDomainParameters domainParams = priKey.getParameters(); + ECParameterSpec spec = + new ECParameterSpec( + domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH()); + BCECPublicKey publicKey = null; + if (pubKey != null) { + publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION); + } + BCECPrivateKey privateKey = + new BCECPrivateKey( + ALGO_NAME_EC, priKey, publicKey, spec, BouncyCastleProvider.CONFIGURATION); + return privateKey.getEncoded(); + } + + //将PKCS8标准的私钥字节流转换为私钥对象 + public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key) + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException { + PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key); + KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME); + return (BCECPrivateKey) kf.generatePrivate(peks); + } + + /** + * 将PKCS8标准的私钥字节流转换为PEM + * + * @param encodedKey + * @return 字符串 + * @throws IOException + */ + public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException { + return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey); + } + + /** + * 将PEM格式的私钥转换为PKCS8标准字节流 + * + * @param pemString + * @return PKCS8字节数组 + * @throws IOException + */ + public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException { + return convertPEMToEncodedData(pemString); + } + + /** + * 将ECC私钥转换为SEC1标准的字节流 openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的, + * 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥. 相对RSA私钥的PKCS1标准,ECC私钥的标准为SEC1 + * + * @param priKey + * @param pubKey + * @return SEC1格式的字节数组 + * @throws IOException + */ + public static byte[] convertECPrivateKeyToSEC1( + ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) throws IOException { + byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey); + PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes); + ASN1Encodable encodable = pki.parsePrivateKey(); + ASN1Primitive primitive = encodable.toASN1Primitive(); + byte[] sec1Bytes = primitive.getEncoded(); + return sec1Bytes; + } + + /** + * 将SEC1标准的私钥字节流恢复为PKCS8标准的字节流 + * + * @param sec1Key + * @return PKCS8字节数组 + * @throws IOException + */ + public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException { + /** + * 参考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和 + * org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey,逆向拼装 + */ + X962Parameters params = getDomainParametersFromName(SM2Util.JDK_EC_SPEC, false); + ASN1OctetString privKey = new DEROctetString(sec1Key); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(0)); // 版本号 + v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); // 算法标识 + v.add(privKey); + DERSequence ds = new DERSequence(v); + return ds.getEncoded(ASN1Encoding.DER); + } + + /** + * 将SEC1标准的私钥字节流转为BCECPrivateKey对象 + * + * @param sec1Key + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + * @throws IOException + */ + public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key) + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, + IOException { + PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key)); + KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME); + return (BCECPrivateKey) kf.generatePrivate(peks); + } + + /** + * 将SEC1标准的私钥字节流转为ECPrivateKeyParameters对象 openssl + * i2d_ECPrivateKey函数生成的DER编码的ecc私钥是:SEC1标准的、带有EC_GROUP、带有公钥的, + * 这个工具函数的主要目的就是为了使Java程序能够“识别”openssl生成的ECC私钥 + * + * @param sec1Key + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + * @throws InvalidKeySpecException + */ + public static ECPrivateKeyParameters convertSEC1ToECPrivateKey(byte[] sec1Key) + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, + IOException { + BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key); + return convertPrivateKeyToParameters(privateKey); + } + + /** + * 将ECC公钥对象转换为X509标准的字节流 + * + * @param pubKey + * @return + */ + public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) { + ECDomainParameters domainParams = pubKey.getParameters(); + ECParameterSpec spec = + new ECParameterSpec( + domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH()); + BCECPublicKey publicKey = + new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION); + return publicKey.getEncoded(); + } + + /** + * 将X509标准的公钥字节流转为公钥对象 + * + * @param x509Bytes + * @return + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + */ + public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) + throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException { + X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes); + KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME); + return (BCECPublicKey) kf.generatePublic(eks); + } + + /** + * 将X509标准的公钥字节流转为PEM + * + * @param encodedKey + * @return + * @throws IOException + */ + public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException { + return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey); + } + + /** + * 将PEM格式的公钥转为X509标准的字节流 + * + * @param pemString + * @return + * @throws IOException + */ + public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException { + return convertPEMToEncodedData(pemString); + } + + /** + * copy from BC + * + * @param genSpec + * @return + */ + public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) { + return getDomainParametersFromName(genSpec.getName()); + } + + /** + * copy from BC + * + * @param curveName + * @return + */ + public static X9ECParameters getDomainParametersFromName(String curveName) { + X9ECParameters domainParameters; + try { + if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') { + ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName); + domainParameters = ECUtil.getNamedCurveByOid(oidID); + } else { + if (curveName.indexOf(' ') > 0) { + curveName = curveName.substring(curveName.indexOf(' ') + 1); + domainParameters = ECUtil.getNamedCurveByName(curveName); + } else { + domainParameters = ECUtil.getNamedCurveByName(curveName); + } + } + } catch (IllegalArgumentException ex) { + domainParameters = ECUtil.getNamedCurveByName(curveName); + } + return domainParameters; + } + + /** + * copy from BC + * + * @param ecSpec + * @param withCompression + * @return + */ + public static X962Parameters getDomainParametersFromName( + java.security.spec.ECParameterSpec ecSpec, boolean withCompression) { + X962Parameters params; + + if (ecSpec instanceof ECNamedCurveSpec) { + ASN1ObjectIdentifier curveOid = + ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName()); + if (curveOid == null) { + curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName()); + } + params = new X962Parameters(curveOid); + } else if (ecSpec == null) { + params = new X962Parameters(DERNull.INSTANCE); + } else { + ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve()); + + X9ECParameters ecP = + new X9ECParameters( + curve, + new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression), + ecSpec.getOrder(), + BigInteger.valueOf(ecSpec.getCofactor()), + ecSpec.getCurve().getSeed()); + + //// 如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码,因为高版本的EC5Util.convertPoint没有向下兼容 + /* + X9ECParameters ecP = new X9ECParameters( + curve, + EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression), + ecSpec.getOrder(), + BigInteger.valueOf(ecSpec.getCofactor()), + ecSpec.getCurve().getSeed()); + */ + + params = new X962Parameters(ecP); + } + + return params; + } + + private static String convertEncodedDataToPEM(String type, byte[] encodedData) + throws IOException { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut)); + try { + PemObject pemObj = new PemObject(type, encodedData); + pWrt.writeObject(pemObj); + } finally { + pWrt.close(); + } + return new String(bOut.toByteArray()); + } + + private static byte[] convertPEMToEncodedData(String pemString) throws IOException { + ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes()); + PemReader pRdr = new PemReader(new InputStreamReader(bIn)); + try { + PemObject pemObject = pRdr.readPemObject(); + return pemObject.getContent(); + } finally { + pRdr.close(); + } + } +} diff --git a/src/main/gm/org.zz/gmhelper/GMBaseUtil.java b/src/main/gm/org.zz/gmhelper/GMBaseUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..00de2321b9e3b8f193bb615f72e937776928a28b --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/GMBaseUtil.java @@ -0,0 +1,11 @@ +package org.zz.gmhelper; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.Security; + +public class GMBaseUtil { + static { + Security.addProvider(new BouncyCastleProvider()); + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM2Cipher.java b/src/main/gm/org.zz/gmhelper/SM2Cipher.java new file mode 100644 index 0000000000000000000000000000000000000000..4fdb4070226ceccecd4c1a47525d55f861fc7676 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2Cipher.java @@ -0,0 +1,55 @@ +package org.zz.gmhelper; + +public class SM2Cipher { + /** + * ECC密钥 + */ + private byte[] c1; + + /** + * 真正的密文 + */ + private byte[] c2; + + /** + * 对(c1+c2)的SM3-HASH值 + */ + private byte[] c3; + + /** + * SM2标准的密文,即(c1+c2+c3) + */ + private byte[] cipherText; + + public byte[] getC1() { + return c1; + } + + public void setC1(byte[] c1) { + this.c1 = c1; + } + + public byte[] getC2() { + return c2; + } + + public void setC2(byte[] c2) { + this.c2 = c2; + } + + public byte[] getC3() { + return c3; + } + + public void setC3(byte[] c3) { + this.c3 = c3; + } + + public byte[] getCipherText() { + return cipherText; + } + + public void setCipherText(byte[] cipherText) { + this.cipherText = cipherText; + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM2KeyExchangeUtil.java b/src/main/gm/org.zz/gmhelper/SM2KeyExchangeUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e82ba6a69ac6651b8e40917d227d80881b4d5a5f --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2KeyExchangeUtil.java @@ -0,0 +1,109 @@ +package org.zz.gmhelper; + +import org.bouncycastle.crypto.agreement.SM2KeyExchange; +import org.bouncycastle.crypto.params.*; + +import java.util.Arrays; + +public class SM2KeyExchangeUtil { + /** + * @param initiator true表示发起方,false表示响应方 + * @param keyBits 生成的密钥长度 + * @param selfStaticPriv 己方固定私钥 + * @param selfEphemeralPriv 己方临时私钥 + * @param selfId 己方ID + * @param otherStaticPub 对方固定公钥 + * @param otherEphemeralPub 对方临时公钥 + * @param otherId 对方ID + * @return 返回协商出的密钥,但是这个密钥是没有经过确认的 + */ + public static byte[] calculateKey(boolean initiator, int keyBits, + ECPrivateKeyParameters selfStaticPriv, ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId, + ECPublicKeyParameters otherStaticPub, ECPublicKeyParameters otherEphemeralPub, byte[] otherId) { + SM2KeyExchange exch = new SM2KeyExchange(); + exch.init(new ParametersWithID( + new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), + selfId)); + return exch.calculateKey( + keyBits, + new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId)); + } + + /** + * @param initiator true表示发起方,false表示响应方 + * @param keyBits 生成的密钥长度 + * @param confirmationTag 确认信息,如果是响应方可以为null;如果是发起方则应为响应方的s1 + * @param selfStaticPriv 己方固定私钥 + * @param selfEphemeralPriv 己方临时私钥 + * @param selfId 己方ID + * @param otherStaticPub 对方固定公钥 + * @param otherEphemeralPub 对方临时公钥 + * @param otherId 对方ID + * @return + */ + public static ExchangeResult calculateKeyWithConfirmation(boolean initiator, int keyBits, byte[] confirmationTag, + ECPrivateKeyParameters selfStaticPriv, ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId, + ECPublicKeyParameters otherStaticPub, ECPublicKeyParameters otherEphemeralPub, byte[] otherId) { + SM2KeyExchange exch = new SM2KeyExchange(); + exch.init(new ParametersWithID( + new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), + selfId)); + byte[][] result = exch.calculateKeyWithConfirmation( + keyBits, + confirmationTag, + new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId)); + ExchangeResult confirmResult = new ExchangeResult(); + confirmResult.setKey(result[0]); + if (initiator) { + confirmResult.setS2(result[1]); + } else { + confirmResult.setS1(result[1]); + confirmResult.setS2(result[2]); + } + return confirmResult; + } + + /** + * @param s2 + * @param confirmationTag 实际上是发起方的s2 + * @return + */ + public static boolean responderConfirm(byte[] s2, byte[] confirmationTag) { + return Arrays.equals(s2, confirmationTag); + } + + public static class ExchangeResult { + private byte[] key; + + /** + * 发起方没有s1 + */ + private byte[] s1; + + private byte[] s2; + + public byte[] getKey() { + return key; + } + + public void setKey(byte[] key) { + this.key = key; + } + + public byte[] getS1() { + return s1; + } + + public void setS1(byte[] s1) { + this.s1 = s1; + } + + public byte[] getS2() { + return s2; + } + + public void setS2(byte[] s2) { + this.s2 = s2; + } + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM2KeyPair.java b/src/main/gm/org.zz/gmhelper/SM2KeyPair.java new file mode 100644 index 0000000000000000000000000000000000000000..62cd717e19e0c30c8c763c8004370c6d6d15a263 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2KeyPair.java @@ -0,0 +1,112 @@ +package org.zz.gmhelper; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.util.Map; + +/** + * SM2密钥对Bean + * + * @author Potato + */ +public class SM2KeyPair { + private final ECPublicKeyParameters publicKey; + private final ECPrivateKeyParameters privateKey; + + public SM2KeyPair(ECPublicKeyParameters publicKey, BigInteger privateKey) { + this.publicKey = publicKey; + this.privateKey = new ECPrivateKeyParameters(privateKey, SM2Util.DOMAIN_PARAMS); + } + + public static SM2KeyPair fromJson(String jsonStr) { + Map jo = + new Gson().fromJson(jsonStr, new TypeToken>() { + }.getType()); + String publicKeyStr = jo.get("publicKey").getAsString(); + String privateKeyStr = jo.get("privateKey").getAsString(); + ECPublicKeyParameters point = + BCECUtil.createECPublicKeyFromStrParameters( + publicKeyStr, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + return new SM2KeyPair(point, new BigInteger(privateKeyStr, 16)); + } + + public static ECPublicKeyParameters publicKeyStr2ECPoint(String pubKey) { + return BCECUtil.createECPublicKeyFromStrParameters( + pubKey, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + } + + public ECPublicKeyParameters getPublicKey() { + return publicKey; + } + + public String getPublicKeyStr() { + return ByteUtils.toHexString(publicKey.getQ().getEncoded(false)); + } + + public BigInteger getPrivateKey() { + return privateKey.getD(); + } + + public ECPrivateKeyParameters getPrivateKeyParameter() { + return privateKey; + } + + public String toJson() { + String ret = "{\"publicKey\":\""; + ret += ByteUtils.toHexString(publicKey.getQ().getEncoded(false)); + ret += "\",\"privateKey\":\""; + ret += privateKey.getD().toString(16); + ret += "\"}"; + return ret; + } + + public String getPrivateKeyStr() { + return privateKey.getD().toString(16); + } + + public KeyPair toJavaSecurity() { + ECDomainParameters domainParams = privateKey.getParameters(); + ECParameterSpec spec = + new ECParameterSpec( + domainParams.getCurve(), + domainParams.getG(), + domainParams.getN(), + domainParams.getH()); + BCECPublicKey bcPublicKey = null; + BCECPrivateKey bcPrivateKey; + if (null != publicKey) { + bcPublicKey = + new BCECPublicKey( + BCECUtil.ALGO_NAME_EC, + publicKey, + spec, + BouncyCastleProvider.CONFIGURATION); + } + bcPrivateKey = + new BCECPrivateKey( + BCECUtil.ALGO_NAME_EC, + privateKey, + bcPublicKey, + spec, + BouncyCastleProvider.CONFIGURATION); + return new KeyPair(bcPublicKey, bcPrivateKey); + } + + public byte[] toX509DEREncoded() { + return BCECUtil.convertECPublicKeyToX509(publicKey); + } + +} diff --git a/src/main/gm/org.zz/gmhelper/SM2PreprocessSigner.java b/src/main/gm/org.zz/gmhelper/SM2PreprocessSigner.java new file mode 100644 index 0000000000000000000000000000000000000000..fb980dd627033402d0b66fee1f1b8e6caec87ca6 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2PreprocessSigner.java @@ -0,0 +1,274 @@ +package org.zz.gmhelper; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.signers.DSAKCalculator; +import org.bouncycastle.crypto.signers.RandomDSAKCalculator; +import org.bouncycastle.math.ec.*; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * 有的国密需求是用户可以自己做预处理,签名验签只是对预处理的结果进行签名和验签 + */ +public class SM2PreprocessSigner implements ECConstants { + private static final int DIGEST_LENGTH = 32; // bytes + + private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); + private Digest digest = null; + + private ECDomainParameters ecParams; + private ECPoint pubPoint; + private ECKeyParameters ecKey; + private byte[] userID; + + /** + * 初始化 + * + * @param forSigning true表示用于签名,false表示用于验签 + * @param param + */ + public void init(boolean forSigning, CipherParameters param) { + init(forSigning, new SM3Digest(), param); + } + + /** + * 初始化 + * + * @param forSigning true表示用于签名,false表示用于验签 + * @param digest SM2算法的话,一般是采用SM3摘要算法 + * @param param + * @throws RuntimeException + */ + public void init(boolean forSigning, Digest digest, CipherParameters param) throws RuntimeException { + CipherParameters baseParam; + + if (digest.getDigestSize() != DIGEST_LENGTH) { + throw new RuntimeException("Digest size must be " + DIGEST_LENGTH); + } + this.digest = digest; + + if (param instanceof ParametersWithID) { + baseParam = ((ParametersWithID) param).getParameters(); + userID = ((ParametersWithID) param).getID(); + } else { + baseParam = param; + userID = Hex.decode("31323334353637383132333435363738"); // the default value + } + + if (forSigning) { + if (baseParam instanceof ParametersWithRandom) { + ParametersWithRandom rParam = (ParametersWithRandom) baseParam; + + ecKey = (ECKeyParameters) rParam.getParameters(); + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), rParam.getRandom()); + } else { + ecKey = (ECKeyParameters) baseParam; + ecParams = ecKey.getParameters(); + kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom()); + } + pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters) ecKey).getD()).normalize(); + } else { + ecKey = (ECKeyParameters) baseParam; + ecParams = ecKey.getParameters(); + pubPoint = ((ECPublicKeyParameters) ecKey).getQ(); + } + } + + /** + * 预处理,辅助方法 + * ZA=H256(ENT LA ∥ IDA ∥ a ∥ b ∥ xG ∥yG ∥ xA ∥ yA)。 + * M=ZA ∥ M; + * e = Hv(M) + * + * @return + */ + public byte[] preprocess(byte[] m, int off, int len) { + byte[] z = getZ(userID); + digest.update(z, 0, z.length); + digest.update(m, off, len); + byte[] eHash = new byte[DIGEST_LENGTH]; + digest.doFinal(eHash, 0); + return eHash; + } + + public boolean verifySignature(byte[] eHash, byte[] signature) { + try { + BigInteger[] rs = derDecode(signature); + if (rs != null) { + return verifySignature(eHash, rs[0], rs[1]); + } + } catch (IOException e) { + } + + return false; + } + + public void reset() { + digest.reset(); + } + + public byte[] generateSignature(byte[] eHash) throws CryptoException { + BigInteger n = ecParams.getN(); + BigInteger e = calculateE(eHash); + BigInteger d = ((ECPrivateKeyParameters) ecKey).getD(); + + BigInteger r, s; + + ECMultiplier basePointMultiplier = createBasePointMultiplier(); + + // 5.2.1 Draft RFC: SM2 Public Key Algorithms + do // generate s + { + BigInteger k; + do // generate r + { + // A3 + k = kCalculator.nextK(); + + // A4 + ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize(); + + // A5 + r = e.add(p.getAffineXCoord().toBigInteger()).mod(n); + } + while (r.equals(ZERO) || r.add(k).equals(n)); + + // A6 + BigInteger dPlus1ModN = d.add(ONE).modInverse(n); + + s = k.subtract(r.multiply(d)).mod(n); + s = dPlus1ModN.multiply(s).mod(n); + } + while (s.equals(ZERO)); + + // A7 + try { + return derEncode(r, s); + } catch (IOException ex) { + throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex); + } + } + + private boolean verifySignature(byte[] eHash, BigInteger r, BigInteger s) { + BigInteger n = ecParams.getN(); + + // 5.3.1 Draft RFC: SM2 Public Key Algorithms + // B1 + if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) { + return false; + } + + // B2 + if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) { + return false; + } + + // B3 eHash + + // B4 + BigInteger e = calculateE(eHash); + + // B5 + BigInteger t = r.add(s).mod(n); + if (t.equals(ZERO)) { + return false; + } + + // B6 + ECPoint q = ((ECPublicKeyParameters) ecKey).getQ(); + ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize(); + if (x1y1.isInfinity()) { + return false; + } + + // B7 + BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n); + + return expectedR.equals(r); + } + + private byte[] digestDoFinal() { + byte[] result = new byte[digest.getDigestSize()]; + digest.doFinal(result, 0); + + reset(); + + return result; + } + + private byte[] getZ(byte[] userID) { + digest.reset(); + + addUserID(digest, userID); + + addFieldElement(digest, ecParams.getCurve().getA()); + addFieldElement(digest, ecParams.getCurve().getB()); + addFieldElement(digest, ecParams.getG().getAffineXCoord()); + addFieldElement(digest, ecParams.getG().getAffineYCoord()); + addFieldElement(digest, pubPoint.getAffineXCoord()); + addFieldElement(digest, pubPoint.getAffineYCoord()); + + byte[] result = new byte[digest.getDigestSize()]; + + digest.doFinal(result, 0); + + return result; + } + + private void addUserID(Digest digest, byte[] userID) { + int len = userID.length * 8; + digest.update((byte) (len >> 8 & 0xFF)); + digest.update((byte) (len & 0xFF)); + digest.update(userID, 0, userID.length); + } + + private void addFieldElement(Digest digest, ECFieldElement v) { + byte[] p = v.getEncoded(); + digest.update(p, 0, p.length); + } + + protected ECMultiplier createBasePointMultiplier() { + return new FixedPointCombMultiplier(); + } + + protected BigInteger calculateE(byte[] message) { + return new BigInteger(1, message); + } + + protected BigInteger[] derDecode(byte[] encoding) + throws IOException { + ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(encoding)); + if (seq.size() != 2) { + return null; + } + + BigInteger r = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue(); + BigInteger s = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue(); + + byte[] expectedEncoding = derEncode(r, s); + if (!Arrays.constantTimeAreEqual(expectedEncoding, encoding)) { + return null; + } + + return new BigInteger[]{r, s}; + } + + protected byte[] derEncode(BigInteger r, BigInteger s) + throws IOException { + + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + return new DERSequence(v).getEncoded(ASN1Encoding.DER); + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM2Signer.java b/src/main/gm/org.zz/gmhelper/SM2Signer.java new file mode 100644 index 0000000000000000000000000000000000000000..ad6b819115c3b8d5151ac8a2e72ca7109b5b9e75 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2Signer.java @@ -0,0 +1,213 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package org.zz.gmhelper; + +import org.bouncycastle.crypto.*; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.signers.DSAEncoding; +import org.bouncycastle.crypto.signers.DSAKCalculator; +import org.bouncycastle.crypto.signers.RandomDSAKCalculator; +import org.bouncycastle.crypto.signers.StandardDSAEncoding; +import org.bouncycastle.math.ec.*; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; + +import java.math.BigInteger; + +public class SM2Signer implements Signer, ECConstants { + private final DSAKCalculator kCalculator; + private final Digest digest; + private final DSAEncoding encoding; + private ECDomainParameters ecParams; + private ECPoint pubPoint; + private ECKeyParameters ecKey; + private byte[] z; + + public SM2Signer() { + this(StandardDSAEncoding.INSTANCE, new SM3Digest()); + } + + public SM2Signer(Digest var1) { + this(StandardDSAEncoding.INSTANCE, var1); + } + + public SM2Signer(DSAEncoding var1) { + this.kCalculator = new RandomDSAKCalculator(); + this.encoding = var1; + this.digest = new SM3Digest(); + } + + public SM2Signer(DSAEncoding var1, Digest var2) { + this.kCalculator = new RandomDSAKCalculator(); + this.encoding = var1; + this.digest = var2; + } + + public void init(boolean var1, CipherParameters var2) { + CipherParameters var3; + byte[] var4; + if (var2 instanceof ParametersWithID) { + var3 = ((ParametersWithID) var2).getParameters(); + var4 = ((ParametersWithID) var2).getID(); + if (var4.length >= 8192) { + throw new IllegalArgumentException("SM2 user ID must be less than 2^16 bits long"); + } + } else { + var3 = var2; + var4 = Hex.decodeStrict("31323334353637383132333435363738"); + } + + if (var1) { + if (var3 instanceof ParametersWithRandom) { + ParametersWithRandom var5 = (ParametersWithRandom) var3; + this.ecKey = (ECKeyParameters) var5.getParameters(); + this.ecParams = this.ecKey.getParameters(); + this.kCalculator.init(this.ecParams.getN(), var5.getRandom()); + } else { + this.ecKey = (ECKeyParameters) var3; + this.ecParams = this.ecKey.getParameters(); + this.kCalculator.init(this.ecParams.getN(), CryptoServicesRegistrar.getSecureRandom()); + } + + this.pubPoint = + this.createBasePointMultiplier() + .multiply(this.ecParams.getG(), ((ECPrivateKeyParameters) this.ecKey).getD()) + .normalize(); + } else { + this.ecKey = (ECKeyParameters) var3; + this.ecParams = this.ecKey.getParameters(); + this.pubPoint = ((ECPublicKeyParameters) this.ecKey).getQ(); + } + + this.z = this.getZ(var4); + this.digest.update(this.z, 0, this.z.length); + } + + public void update(byte var1) { + this.digest.update(var1); + } + + public void update(byte[] var1, int var2, int var3) { + this.digest.update(var1, var2, var3); + } + + public boolean verifySignature(byte[] var1) { + try { + BigInteger[] var2 = this.encoding.decode(this.ecParams.getN(), var1); + return this.verifySignature(var2[0], var2[1]); + } catch (Exception var3) { + return false; + } + } + + public void reset() { + this.digest.reset(); + if (this.z != null) { + this.digest.update(this.z, 0, this.z.length); + } + } + + public byte[] generateSignature() throws CryptoException { + byte[] var1 = this.digestDoFinal(); + BigInteger n = this.ecParams.getN(); + BigInteger var3 = this.calculateE(n , var1); + BigInteger var4 = ((ECPrivateKeyParameters) this.ecKey).getD(); + ECMultiplier var7 = this.createBasePointMultiplier(); + + while (true) { + BigInteger var5; + BigInteger k; + do { + k = this.kCalculator.nextK(); + ECPoint var9 = var7.multiply(this.ecParams.getG(), k).normalize(); + var5 = var3.add(var9.getAffineXCoord().toBigInteger()).mod(n); + } while (var5.equals(ZERO)); + + if (!var5.add(k).equals(n )) { + BigInteger var11 = BigIntegers.modOddInverse(n, var4.add(ONE)); + BigInteger var6 = k.subtract(var5.multiply(var4)).mod(n); + var6 = var11.multiply(var6).mod(n); + if (!var6.equals(ZERO)) { + try { + return this.encoding.encode(this.ecParams.getN(), var5, var6); + } catch (Exception var10) { + throw new CryptoException("unable to encode signature: " + var10.getMessage(), var10); + } + } + } + } + } + + private boolean verifySignature(BigInteger var1, BigInteger var2) { + BigInteger var3 = this.ecParams.getN(); + if (var1.compareTo(ONE) >= 0 && var1.compareTo(var3) < 0) { + if (var2.compareTo(ONE) >= 0 && var2.compareTo(var3) < 0) { + byte[] var4 = this.digestDoFinal(); + BigInteger var5 = this.calculateE(var3, var4); + BigInteger var6 = var1.add(var2).mod(var3); + if (var6.equals(ZERO)) { + return false; + } else { + ECPoint var7 = ((ECPublicKeyParameters) this.ecKey).getQ(); + ECPoint var8 = + ECAlgorithms.sumOfTwoMultiplies(this.ecParams.getG(), var2, var7, var6).normalize(); + if (var8.isInfinity()) { + return false; + } else { + BigInteger var9 = var5.add(var8.getAffineXCoord().toBigInteger()).mod(var3); + return var9.equals(var1); + } + } + } else { + return false; + } + } else { + return false; + } + } + + private byte[] digestDoFinal() { + byte[] var1 = new byte[this.digest.getDigestSize()]; + this.digest.doFinal(var1, 0); + this.reset(); + return var1; + } + + private byte[] getZ(byte[] var1) { + this.digest.reset(); + this.addUserID(this.digest, var1); + this.addFieldElement(this.digest, this.ecParams.getCurve().getA()); + this.addFieldElement(this.digest, this.ecParams.getCurve().getB()); + this.addFieldElement(this.digest, this.ecParams.getG().getAffineXCoord()); + this.addFieldElement(this.digest, this.ecParams.getG().getAffineYCoord()); + this.addFieldElement(this.digest, this.pubPoint.getAffineXCoord()); + this.addFieldElement(this.digest, this.pubPoint.getAffineYCoord()); + byte[] var2 = new byte[this.digest.getDigestSize()]; + this.digest.doFinal(var2, 0); + return var2; + } + + private void addUserID(Digest var1, byte[] var2) { + int var3 = var2.length * 8; + var1.update((byte) (var3 >> 8 & 255)); + var1.update((byte) (var3 & 255)); + var1.update(var2, 0, var2.length); + } + + private void addFieldElement(Digest var1, ECFieldElement var2) { + byte[] var3 = var2.getEncoded(); + var1.update(var3, 0, var3.length); + } + + protected ECMultiplier createBasePointMultiplier() { + return new FixedPointCombMultiplier(); + } + + protected BigInteger calculateE(BigInteger var1, byte[] var2) { + return new BigInteger(1, var2); + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM2Util.java b/src/main/gm/org.zz/gmhelper/SM2Util.java new file mode 100644 index 0000000000000000000000000000000000000000..77ba837ccef50dd75d653f57018776041e76b3ef --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM2Util.java @@ -0,0 +1,664 @@ +package org.zz.gmhelper; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.engines.SM2Engine.Mode; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.spec.ECFieldFp; +import java.security.spec.EllipticCurve; + +public class SM2Util extends GMBaseUtil { + ////////////////////////////////////////////////////////////////////////////////////// + /* + * 以下为SM2推荐曲线参数 + */ + public static final SM2P256V1Curve CURVE = new SM2P256V1Curve(); + public static final BigInteger SM2_ECC_P = CURVE.getQ(); + public static final BigInteger SM2_ECC_A = CURVE.getA().toBigInteger(); + public static final BigInteger SM2_ECC_B = CURVE.getB().toBigInteger(); + public static final BigInteger SM2_ECC_N = CURVE.getOrder(); + public static final BigInteger SM2_ECC_H = CURVE.getCofactor(); + public static final BigInteger SM2_ECC_GX = + new BigInteger("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + public static final BigInteger SM2_ECC_GY = + new BigInteger("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); + public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY); + public static final ECDomainParameters DOMAIN_PARAMS = + new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H); + public static final int CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS); + ////////////////////////////////////////////////////////////////////////////////////// + public static final EllipticCurve JDK_CURVE = + new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B); + public static final java.security.spec.ECPoint JDK_G_POINT = + new java.security.spec.ECPoint( + G_POINT.getAffineXCoord().toBigInteger(), + G_POINT.getAffineYCoord().toBigInteger()); + public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = + new java.security.spec.ECParameterSpec( + JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue()); + public static final int SM3_DIGEST_LENGTH = 32; + + ////////////////////////////////////////////////////////////////////////////////////// + static ECCurve.Fp SM2_ECC_FP = + new ECCurve.Fp( + SM2_ECC_P, // q + SM2_ECC_A, // a + SM2_ECC_B); // b + + /** + * 生成ECC密钥对 + * + * @return ECC密钥对 + */ + public static AsymmetricCipherKeyPair generateKeyPairParameter() { + SecureRandom random = new SecureRandom(); + return BCECUtil.generateKeyPairParameter(DOMAIN_PARAMS, random); + } + + /** + * generate ECC key pair + * + * @param random + * @return + * @throws NoSuchProviderException + * @throws NoSuchAlgorithmException + * @throws InvalidAlgorithmParameterException + */ + public static KeyPair generateKeyPair(SecureRandom random) + throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException { + return BCECUtil.generateKeyPair(DOMAIN_PARAMS, random); + } + + public static SM2KeyPair generateSM2KeyPair() { + return generateSM2KeyPair(null); + } + + public static SM2KeyPair generateSM2KeyPair(SecureRandom r) { + try { + KeyPair key = generateKeyPair(r); + BCECPrivateKey privateKey = (BCECPrivateKey) key.getPrivate(); + BCECPublicKey publicKey = (BCECPublicKey) key.getPublic(); + byte[] point = publicKey.getQ().getEncoded(false); + ECPublicKeyParameters parameters = + BCECUtil.createECPublicKeyFromStrParameters( + ByteUtils.toHexString(point), CURVE, DOMAIN_PARAMS); + return new SM2KeyPair(parameters, privateKey.getD()); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 只获取私钥里的d值,32字节 + * + * @param privateKey + * @return + */ + public static byte[] getRawPrivateKey(BCECPrivateKey privateKey) { + return fixToCurveLengthBytes(privateKey.getD().toByteArray()); + } + + /** + * 只获取公钥里的XY分量,64字节 + * + * @param publicKey + * @return 64字节数组 + */ + public static byte[] getRawPublicKey(BCECPublicKey publicKey) { + byte[] src65 = publicKey.getQ().getEncoded(false); + byte[] rawXY = new byte[CURVE_LEN * 2]; // SM2的话这里应该是64字节 + System.arraycopy(src65, 1, rawXY, 0, rawXY.length); + return rawXY; + } + + /** + * @param pubKey 公钥 + * @param srcData 原文 + * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @throws InvalidCipherTextException + */ + public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) + throws InvalidCipherTextException { + ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey); + return encrypt(Mode.C1C3C2, pubKeyParameters, srcData); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param pubKey 公钥 + * @param srcData 原文 + * @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @throws InvalidCipherTextException + */ + public static byte[] encrypt(Mode mode, BCECPublicKey pubKey, byte[] srcData) + throws InvalidCipherTextException { + ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey); + return encrypt(mode, pubKeyParameters, srcData); + } + + /** + * @param pubKeyParameters 公钥 + * @param srcData 原文 + * @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @throws InvalidCipherTextException + */ + public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData) + throws InvalidCipherTextException { + return encrypt(Mode.C1C3C2, pubKeyParameters, srcData); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param pubKeyParameters 公钥 + * @param srcData 原文 + * @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @throws InvalidCipherTextException + */ + public static byte[] encrypt(Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData) + throws InvalidCipherTextException { + SM2Engine engine = new SM2Engine(mode); + ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom()); + engine.init(true, pwr); + return engine.processBlock(srcData, 0, srcData.length); + } + + /** + * @param priKey 私钥 + * @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。 + * @throws InvalidCipherTextException + */ + public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) + throws InvalidCipherTextException { + ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey); + return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param priKey 私钥 + * @param sm2Cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。 + * @throws InvalidCipherTextException + */ + public static byte[] decrypt(Mode mode, BCECPrivateKey priKey, byte[] sm2Cipher) + throws InvalidCipherTextException { + ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey); + return decrypt(mode, priKeyParameters, sm2Cipher); + } + + /** + * @param priKeyParameters 私钥 + * @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。 + * @throws InvalidCipherTextException + */ + public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher) + throws InvalidCipherTextException { + return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param priKeyParameters 私钥 + * @param sm2Cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。 + * @throws InvalidCipherTextException + */ + public static byte[] decrypt( + Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher) + throws InvalidCipherTextException { + SM2Engine engine = new SM2Engine(mode); + engine.init(false, priKeyParameters); + if (sm2Cipher[0] != 4) { + byte[] cipher2 = new byte[sm2Cipher.length + 1]; + System.arraycopy(sm2Cipher, 0, cipher2, 1, sm2Cipher.length); + cipher2[0] = 4; + sm2Cipher = cipher2; + } + return engine.processBlock(sm2Cipher, 0, sm2Cipher.length); + } + + /** + * 分解SM2密文 + * + * @param cipherText 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return + * @throws Exception + */ + public static SM2Cipher parseSM2Cipher(byte[] cipherText) throws Exception { + int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS); + return parseSM2Cipher(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipherText); + } + + /** + * 分解SM2密文 + * + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param cipherText 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return + */ + public static SM2Cipher parseSM2Cipher(Mode mode, byte[] cipherText) throws Exception { + int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS); + return parseSM2Cipher(mode, curveLength, SM3_DIGEST_LENGTH, cipherText); + } + + /** + * @param curveLength 曲线长度,SM2的话就是256位。 + * @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。 + * @param cipherText 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return + * @throws Exception + */ + public static SM2Cipher parseSM2Cipher(int curveLength, int digestLength, byte[] cipherText) + throws Exception { + return parseSM2Cipher(Mode.C1C3C2, curveLength, digestLength, cipherText); + } + + /** + * 分解SM2密文 + * + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param curveLength 曲线长度,SM2的话就是256位。 + * @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。 + * @param cipherText 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return + */ + public static SM2Cipher parseSM2Cipher( + Mode mode, int curveLength, int digestLength, byte[] cipherText) throws Exception { + byte[] c1 = new byte[curveLength * 2 + 1]; + byte[] c2 = new byte[cipherText.length - c1.length - digestLength]; + byte[] c3 = new byte[digestLength]; + + System.arraycopy(cipherText, 0, c1, 0, c1.length); + if (mode == Mode.C1C2C3) { + System.arraycopy(cipherText, c1.length, c2, 0, c2.length); + System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c3.length); + } else if (mode == Mode.C1C3C2) { + System.arraycopy(cipherText, c1.length, c3, 0, c3.length); + System.arraycopy(cipherText, c1.length + c3.length, c2, 0, c2.length); + } else { + throw new Exception("Unsupported mode:" + mode); + } + + SM2Cipher result = new SM2Cipher(); + result.setC1(c1); + result.setC2(c2); + result.setC3(c3); + result.setCipherText(cipherText); + return result; + } + + /** + * DER编码密文 + * + * @param cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return DER编码后的密文 + * @throws IOException + */ + public static byte[] encodeSM2CipherToDER(byte[] cipher) throws Exception { + int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS); + return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipher); + } + + /** + * DER编码密文 + * + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 按指定mode DER编码后的密文 + * @throws Exception + */ + public static byte[] encodeSM2CipherToDER(Mode mode, byte[] cipher) throws Exception { + int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS); + return encodeSM2CipherToDER(mode, curveLength, SM3_DIGEST_LENGTH, cipher); + } + + /** + * DER编码密文 + * + * @param curveLength 曲线长度,SM2的话就是256位。 + * @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。 + * @param cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 默认输出按C1C3C2编码的结果 + * @throws IOException + */ + public static byte[] encodeSM2CipherToDER(int curveLength, int digestLength, byte[] cipher) + throws Exception { + return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, digestLength, cipher); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param curveLength 曲线长度,SM2的话就是256位。 + * @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。 + * @param cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @return 按指定mode DER编码后的密文 + * @throws Exception + */ + public static byte[] encodeSM2CipherToDER( + Mode mode, int curveLength, int digestLength, byte[] cipher) throws Exception { + + byte[] c1x = new byte[curveLength]; + byte[] c1y = new byte[curveLength]; + byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength]; + byte[] c3 = new byte[digestLength]; + + int startPos = 1; + System.arraycopy(cipher, startPos, c1x, 0, c1x.length); + startPos += c1x.length; + System.arraycopy(cipher, startPos, c1y, 0, c1y.length); + startPos += c1y.length; + if (mode == Mode.C1C2C3) { + System.arraycopy(cipher, startPos, c2, 0, c2.length); + startPos += c2.length; + System.arraycopy(cipher, startPos, c3, 0, c3.length); + } else if (mode == Mode.C1C3C2) { + System.arraycopy(cipher, startPos, c3, 0, c3.length); + startPos += c3.length; + System.arraycopy(cipher, startPos, c2, 0, c2.length); + } else { + throw new Exception("Unsupported mode:" + mode); + } + + ASN1Encodable[] arr = new ASN1Encodable[4]; + arr[0] = new ASN1Integer(new BigInteger(1,c1x)); + arr[1] = new ASN1Integer(new BigInteger(1,c1y)); + if (mode == Mode.C1C2C3) { + arr[2] = new DEROctetString(c2); + arr[3] = new DEROctetString(c3); + } else if (mode == Mode.C1C3C2) { + arr[2] = new DEROctetString(c3); + arr[3] = new DEROctetString(c2); + } + DERSequence ds = new DERSequence(arr); + return ds.getEncoded(ASN1Encoding.DER); + } + + /** + * 解码DER密文 + * + * @param derCipher 默认输入按C1C3C2顺序DER编码的密文 + * @return 输出按C1C3C2排列的字节数组,C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + */ + public static byte[] decodeDERSM2Cipher(byte[] derCipher) throws Exception { + return decodeDERSM2Cipher(Mode.C1C3C2, derCipher); + } + + /** + * @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + * @param derCipher 根据mode输入C1C2C3或C1C3C2顺序DER编码后的密文 + * @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + * @throws Exception + */ + public static byte[] decodeDERSM2Cipher(Mode mode, byte[] derCipher) throws Exception { + ASN1Sequence as = DERSequence.getInstance(derCipher); + byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray(); + byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray(); + byte[] c3; + byte[] c2; + c1x = fixToCurveLengthBytes(c1x); + c1y = fixToCurveLengthBytes(c1y); + if (mode == Mode.C1C2C3) { + c2 = ((DEROctetString) as.getObjectAt(2)).getOctets(); + c3 = ((DEROctetString) as.getObjectAt(3)).getOctets(); + } else if (mode == Mode.C1C3C2) { + c3 = ((DEROctetString) as.getObjectAt(2)).getOctets(); + c2 = ((DEROctetString) as.getObjectAt(3)).getOctets(); + } else { + throw new Exception("Unsupported mode:" + mode); + } + + int pos = 0; + byte[] cipherText = new byte[1 + c1x.length + c1y.length + c2.length + c3.length]; + final byte uncompressedFlag = 0x04; + cipherText[0] = uncompressedFlag; + pos += 1; + System.arraycopy(c1x, 0, cipherText, pos, c1x.length); + pos += c1x.length; + System.arraycopy(c1y, 0, cipherText, pos, c1y.length); + pos += c1y.length; + if (mode == Mode.C1C2C3) { + System.arraycopy(c2, 0, cipherText, pos, c2.length); + pos += c2.length; + System.arraycopy(c3, 0, cipherText, pos, c3.length); + } else if (mode == Mode.C1C3C2) { + System.arraycopy(c3, 0, cipherText, pos, c3.length); + pos += c3.length; + System.arraycopy(c2, 0, cipherText, pos, c2.length); + } + return cipherText; + } + + /** + * 签名 + * + * @param priKey 私钥 + * @param srcData 原文 + * @return DER编码后的签名值 + * @throws CryptoException + */ + public static byte[] sign(BCECPrivateKey priKey, byte[] srcData) throws CryptoException { + ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey); + return sign(priKeyParameters, null, srcData); + } + + /** + * 签名 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes() + * + * @param priKeyParameters 私钥 + * @param srcData 原文 + * @return DER编码后的签名值 + * @throws CryptoException + */ + public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) + throws CryptoException { + return sign(priKeyParameters, null, srcData); + } + + /** + * 私钥签名 + * + * @param priKey 私钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 原文 + * @return DER编码后的签名值 + * @throws CryptoException + */ + public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) + throws CryptoException { + ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey); + return sign(priKeyParameters, withId, srcData); + } + + /** + * 签名 + * + * @param priKeyParameters 私钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 源数据 + * @return DER编码后的签名值 + * @throws CryptoException + */ + public static byte[] sign( + ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData) + throws CryptoException { + SM2Signer signer = new SM2Signer(); + CipherParameters param = null; + ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom()); + if (withId != null) { + param = new ParametersWithID(pwr, withId); + } else { + param = pwr; + } + signer.init(true, param); + signer.update(srcData, 0, srcData.length); + return signer.generateSignature(); + } + + /** + * 将DER编码的SM2签名解码成64字节的纯R+S字节流 + * + * @param derSign + * @return 64字节数组,前32字节为R,后32字节为S + */ + public static byte[] decodeDERSM2Sign(byte[] derSign) { + ASN1Sequence as = DERSequence.getInstance(derSign); + byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray(); + byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray(); + // 由于大数的补0规则,所以可能会出现33个字节的情况,要修正回32个字节 + rBytes = fixToCurveLengthBytes(rBytes); + sBytes = fixToCurveLengthBytes(sBytes); + byte[] rawSign = new byte[rBytes.length + sBytes.length]; + System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length); + System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length); + return rawSign; + } + + /** + * 把64字节的纯R+S字节数组编码成DER编码 + * + * @param rawSign 64字节数组形式的SM2签名值,前32字节为R,后32字节为S + * @return DER编码后的SM2签名值 + * @throws IOException + */ + public static byte[] encodeSM2SignToDER(byte[] rawSign) throws IOException { + // 要保证大数是正数 + BigInteger r = new BigInteger(1, extractBytes(rawSign, 0, 32)); + BigInteger s = new BigInteger(1, extractBytes(rawSign, 32, 32)); + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + return new DERSequence(v).getEncoded(ASN1Encoding.DER); + } + + /** + * 把64字节的纯R+S字节数组编码成DER编码 + * + * @param str 64字节数组形式的SM2签名值,前32字节为R,后32字节为S + * @return DER编码后的SM2签名值 + * @throws IOException + */ + public static byte[] encodeSM2Sign16ToDER(String str) throws IOException { + // 要保证大数是正数 + BigInteger r, s; + if (str.indexOf(",") != -1) { + r = new BigInteger(str.substring(0, 32), 16); + s = new BigInteger(str.substring(33, 65), 16); + } else { + r = new BigInteger(str.substring(0, 32), 16); + s = new BigInteger(str.substring(32, 64), 16); + } + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(new ASN1Integer(r)); + v.add(new ASN1Integer(s)); + return new DERSequence(v).getEncoded(ASN1Encoding.DER); + } + + /** + * 验签 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes() + * + * @param pubKeyParameters 公钥 + * @param srcData 原文 + * @param sign DER编码的签名值 + * @return 验签成功返回true,失败返回false + */ + public static boolean verify( + ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) { + return verify(pubKeyParameters, null, srcData, sign); + } + + /** + * 验签 + * + * @param pubKey 公钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 原文 + * @param sign DER编码的签名值 + * @return + */ + public static boolean verify(BCECPublicKey pubKey, byte[] withId, byte[] srcData, byte[] sign) { + ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey); + return verify(pubKeyParameters, withId, srcData, sign); + } + + /** + * 验签 + * + * @param pubKeyParameters 公钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 原文 + * @param sign DER编码的签名值 + * @return 验签成功返回true,失败返回false + */ + public static boolean verify( + ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) { + SM2Signer signer = new SM2Signer(); + CipherParameters param; + if (withId != null) { + param = new ParametersWithID(pubKeyParameters, withId); + } else { + param = pubKeyParameters; + } + signer.init(false, param); + signer.update(srcData, 0, srcData.length); + return signer.verifySignature(sign); + } + + private static byte[] extractBytes(byte[] src, int offset, int length) { + byte[] result = new byte[length]; + System.arraycopy(src, offset, result, 0, result.length); + return result; + } + + private static byte[] fixToCurveLengthBytes(byte[] src) { + if (src.length == CURVE_LEN) { + return src; + } + + byte[] result = new byte[CURVE_LEN]; + if (src.length > CURVE_LEN) { + System.arraycopy(src, src.length - result.length, result, 0, result.length); + } else { + System.arraycopy(src, 0, result, result.length - src.length, src.length); + } + return result; + } + + public static boolean plainStrVerify(String publicKey, String toVerify, String signature) { + try { + byte[] sigByte = ByteUtils.fromHexString(signature); + // TODO 是否需要der编码? + // try { + // sigByte = SM2Util.encodeSM2SignToDER(sigByte); + // } catch (Exception e) { + // e.printStackTrace(); + // } + ECPublicKeyParameters pubkey = + BCECUtil.createECPublicKeyFromStrParameters( + publicKey, SM2Util.CURVE, SM2Util.DOMAIN_PARAMS); + boolean result = SM2Util.verify(pubkey, toVerify.getBytes(), sigByte); + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM3Util.java b/src/main/gm/org.zz/gmhelper/SM3Util.java new file mode 100644 index 0000000000000000000000000000000000000000..83b604e00178c4030833d2610081f92a03265d2e --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM3Util.java @@ -0,0 +1,58 @@ +package org.zz.gmhelper; + +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.util.Arrays; + +public class SM3Util extends GMBaseUtil { + + /** + * 计算SM3摘要值 + * + * @param srcData 原文 + * @return 摘要值,对于SM3算法来说是32字节 + */ + public static byte[] hash(byte[] srcData) { + SM3Digest digest = new SM3Digest(); + digest.update(srcData, 0, srcData.length); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + return hash; + } + + /** + * 验证摘要 + * + * @param srcData 原文 + * @param sm3Hash 摘要值 + * @return 返回true标识验证成功,false标识验证失败 + */ + public static boolean verify(byte[] srcData, byte[] sm3Hash) { + byte[] newHash = hash(srcData); + if (Arrays.equals(newHash, sm3Hash)) { + return true; + } else { + return false; + } + } + + /** + * 计算SM3 Mac值 + * + * @param key key值,可以是任意长度的字节数组 + * @param srcData 原文 + * @return Mac值,对于HMac-SM3来说是32字节 + */ + public static byte[] hmac(byte[] key, byte[] srcData) { + KeyParameter keyParameter = new KeyParameter(key); + SM3Digest digest = new SM3Digest(); + HMac mac = new HMac(digest); + mac.init(keyParameter); + mac.update(srcData, 0, srcData.length); + byte[] result = new byte[mac.getMacSize()]; + mac.doFinal(result, 0); + return result; + } +} diff --git a/src/main/gm/org.zz/gmhelper/SM4Util.java b/src/main/gm/org.zz/gmhelper/SM4Util.java new file mode 100644 index 0000000000000000000000000000000000000000..55fd837c43ee54bbedc4d3bab5a930ddc1519a21 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/SM4Util.java @@ -0,0 +1,181 @@ +package org.zz.gmhelper; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.engines.SM4Engine; +import org.bouncycastle.crypto.macs.CBCBlockCipherMac; +import org.bouncycastle.crypto.macs.GMac; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.paddings.BlockCipherPadding; +import org.bouncycastle.crypto.paddings.PKCS7Padding; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.crypto.params.ParametersWithIV; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.*; + +public class SM4Util extends GMBaseUtil { + public static final String ALGORITHM_NAME = "SM4"; + public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; + public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding"; + public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding"; + public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding"; + + /** + * SM4算法目前只支持128位(即密钥16字节) + */ + public static final int DEFAULT_KEY_SIZE = 128; + + public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException { + return generateKey(DEFAULT_KEY_SIZE); + } + + public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException { + KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME); + kg.init(keySize, new SecureRandom()); + return kg.generateKey().getEncoded(); + } + + public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(data); + } + + public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + + public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(data); + } + + public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException { + Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + + public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv); + return cipher.doFinal(data); + } + + public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, + InvalidAlgorithmParameterException { + Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv); + return cipher.doFinal(cipherText); + } + + public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data) + throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, + InvalidAlgorithmParameterException { + Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv); + return cipher.doFinal(data); + } + + public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText) + throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, + NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, + InvalidAlgorithmParameterException { + Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv); + return cipher.doFinal(cipherText); + } + + public static byte[] doCMac(byte[] key, byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException, + InvalidKeyException { + Key keyObj = new SecretKeySpec(key, ALGORITHM_NAME); + return doMac("SM4-CMAC", keyObj, data); + } + + public static byte[] doGMac(byte[] key, byte[] iv, int tagLength, byte[] data) { + org.bouncycastle.crypto.Mac mac = new GMac(new GCMBlockCipher(new SM4Engine()), tagLength * 8); + return doMac(mac, key, iv, data); + } + + /** + * 默认使用PKCS7Padding/PKCS5Padding填充的CBCMAC + * + * @param key + * @param iv + * @param data + * @return + */ + public static byte[] doCBCMac(byte[] key, byte[] iv, byte[] data) { + SM4Engine engine = new SM4Engine(); + org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, new PKCS7Padding()); + return doMac(mac, key, iv, data); + } + + /** + * @param key + * @param iv + * @param padding 可以传null,传null表示NoPadding,由调用方保证数据必须是BlockSize的整数倍 + * @param data + * @return + * @throws Exception + */ + public static byte[] doCBCMac(byte[] key, byte[] iv, BlockCipherPadding padding, byte[] data) throws Exception { + SM4Engine engine = new SM4Engine(); + if (padding == null) { + if (data.length % engine.getBlockSize() != 0) { + throw new Exception("if no padding, data length must be multiple of SM4 BlockSize"); + } + } + org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, padding); + return doMac(mac, key, iv, data); + } + + + private static byte[] doMac(org.bouncycastle.crypto.Mac mac, byte[] key, byte[] iv, byte[] data) { + CipherParameters cipherParameters = new KeyParameter(key); + mac.init(new ParametersWithIV(cipherParameters, iv)); + mac.update(data, 0, data.length); + byte[] result = new byte[mac.getMacSize()]; + mac.doFinal(result, 0); + return result; + } + + private static byte[] doMac(String algorithmName, Key key, byte[] data) throws NoSuchProviderException, + NoSuchAlgorithmException, InvalidKeyException { + Mac mac = Mac.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME); + mac.init(key); + mac.update(data); + return mac.doFinal(); + } + + private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key) + throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, + InvalidKeyException { + Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME); + Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME); + cipher.init(mode, sm4Key); + return cipher; + } + + private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv) + throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, + NoSuchProviderException, NoSuchPaddingException { + Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME); + Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME); + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); + cipher.init(mode, sm4Key, ivParameterSpec); + return cipher; + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/CertSNAllocator.java b/src/main/gm/org.zz/gmhelper/cert/CertSNAllocator.java new file mode 100644 index 0000000000000000000000000000000000000000..df0ab2e49c8d1bf08f2f20b98a7bd0129a4e3656 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/CertSNAllocator.java @@ -0,0 +1,7 @@ +package org.zz.gmhelper.cert; + +import java.math.BigInteger; + +public interface CertSNAllocator { + BigInteger nextSerialNumber() throws Exception; +} diff --git a/src/main/gm/org.zz/gmhelper/cert/CommonUtil.java b/src/main/gm/org.zz/gmhelper/cert/CommonUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..13c71f948ef5438e27e96109285eb86e9334c955 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/CommonUtil.java @@ -0,0 +1,69 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; +import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.zz.gmhelper.cert.exception.InvalidX500NameException; + +import java.security.PrivateKey; +import java.util.Iterator; +import java.util.Map; + +public class CommonUtil { + /** + * 如果不知道怎么填充names,可以查看org.bouncycastle.asn1.x500.style.BCStyle这个类, + * names的key值必须是BCStyle.DefaultLookUp中存在的(可以不关心大小写) + * + * @param names + * @return + * @throws InvalidX500NameException + */ + public static X500Name buildX500Name(Map names) throws InvalidX500NameException { + if (names == null || names.size() == 0) { + throw new InvalidX500NameException("names can not be empty"); + } + try { + X500NameBuilder builder = new X500NameBuilder(); + Iterator itr = names.entrySet().iterator(); + BCStyle x500NameStyle = (BCStyle) BCStyle.INSTANCE; + Map.Entry entry; + while (itr.hasNext()) { + entry = (Map.Entry) itr.next(); + ASN1ObjectIdentifier oid = x500NameStyle.attrNameToOID((String) entry.getKey()); + builder.addRDN(oid, (String) entry.getValue()); + } + return builder.build(); + } catch (Exception ex) { + throw new InvalidX500NameException(ex.getMessage(), ex); + } + } + + public static PKCS10CertificationRequest createCSR(X500Name subject, SM2PublicKey pubKey, PrivateKey priKey, + String signAlgo) throws OperatorCreationException { + PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey); + ContentSigner signerBuilder = new JcaContentSignerBuilder(signAlgo) + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey); + return csrBuilder.build(signerBuilder); + } + + public static AlgorithmIdentifier findSignatureAlgorithmIdentifier(String algoName) { + DefaultSignatureAlgorithmIdentifierFinder sigFinder = new DefaultSignatureAlgorithmIdentifierFinder(); + return sigFinder.find(algoName); + } + + public static AlgorithmIdentifier findDigestAlgorithmIdentifier(String algoName) { + DefaultDigestAlgorithmIdentifierFinder digFinder = new DefaultDigestAlgorithmIdentifierFinder(); + return digFinder.find(findSignatureAlgorithmIdentifier(algoName)); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/FileSNAllocator.java b/src/main/gm/org.zz/gmhelper/cert/FileSNAllocator.java new file mode 100644 index 0000000000000000000000000000000000000000..39209e718c659458b74cd1ddfa613b23465c0ff0 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/FileSNAllocator.java @@ -0,0 +1,48 @@ +package org.zz.gmhelper.cert; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.math.BigInteger; + +public class FileSNAllocator implements CertSNAllocator { + private static final String SN_FILENAME = "sn.dat"; + private static String snFilePath; + + static { + ClassLoader loader = FileSNAllocator.class.getClassLoader(); + snFilePath = loader.getResource(SN_FILENAME).getPath(); + } + + public synchronized BigInteger nextSerialNumber() throws Exception { + BigInteger sn = readSN(); + writeSN(sn.add(BigInteger.ONE)); + return sn; + } + + private BigInteger readSN() throws IOException { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(snFilePath, "r"); + byte[] data = new byte[(int) raf.length()]; + raf.read(data); + String snStr = new String(data); + return new BigInteger(snStr); + } finally { + if (raf != null) { + raf.close(); + } + } + } + + private void writeSN(BigInteger sn) throws IOException { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(snFilePath, "rw"); + raf.writeBytes(sn.toString(10)); + } finally { + if (raf != null) { + raf.close(); + } + } + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/RandomSNAllocator.java b/src/main/gm/org.zz/gmhelper/cert/RandomSNAllocator.java new file mode 100644 index 0000000000000000000000000000000000000000..0d2803d1026c8ca2d97e2e2c196979ce831d8b05 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/RandomSNAllocator.java @@ -0,0 +1,78 @@ +/* + * + * This is simplified version of the RandomSerialNumberGenerator from the project + * https://github.com/xipki/xipki. + */ + +package org.zz.gmhelper.cert; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * Random serial number generator. + * + * This class is thread safe. + * + * @author Lijun Liao + */ +public class RandomSNAllocator implements CertSNAllocator { + + /** + * The highest bit is always set to 1, so the effective bit length is bitLen - 1. To ensure that + * at least 64 bit entropy, bitLen must be at least 65. + */ + private final static int MIN_SERIALNUMBER_SIZE = 65; + + /** + * Since serial number should be positive and maximal 20 bytes, the maximal value of bitLen is + * 159. + */ + private final static int MAX_SERIALNUMBER_SIZE = 159; + + private static int[] AND_MASKS = new int[] {0xFF, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F}; + + private static int[] OR_MASKS = new int[] {0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40}; + + private final SecureRandom random; + + private final int bitLen; + + /** + * Constructor with the bitLen = 65. + */ + public RandomSNAllocator() { + this(MIN_SERIALNUMBER_SIZE); + } + + /** + * Constructor with the specification of bitLen. + * @param bitLen bit length of the serial number. The highest bit is always set to 1, so the + * effective bit length is bitLen - 1. Valid value is [65, 159]. + */ + public RandomSNAllocator(int bitLen) { + if (bitLen < MIN_SERIALNUMBER_SIZE || bitLen > MAX_SERIALNUMBER_SIZE) { + throw new IllegalArgumentException(String.format( + "%s may not be out of the range [%d, %d]: %d", + "bitLen", MIN_SERIALNUMBER_SIZE, MAX_SERIALNUMBER_SIZE, bitLen)); + } + + this.random = new SecureRandom(); + this.bitLen = bitLen; + } + + @Override + public BigInteger nextSerialNumber() { + final byte[] rdnBytes = new byte[(bitLen + 7) / 8]; + final int ci = bitLen % 8; + + random.nextBytes(rdnBytes); + if (ci != 0) { + rdnBytes[0] = (byte) (rdnBytes[0] & AND_MASKS[ci]); + } + rdnBytes[0] = (byte) (rdnBytes[0] | OR_MASKS[ci]); + + return new BigInteger(1, rdnBytes); + } + +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2CertUtil.java b/src/main/gm/org.zz/gmhelper/cert/SM2CertUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..2913a1d4af5a7804112b699ed64f90a4fe3f2d30 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2CertUtil.java @@ -0,0 +1,166 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.pkcs.ContentInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.interfaces.ECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.pkcs.PKCS12PfxPdu; +import org.bouncycastle.pkcs.PKCS12SafeBag; +import org.bouncycastle.pkcs.PKCS12SafeBagFactory; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; +import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder; +import org.zz.gmhelper.BCECUtil; +import org.zz.gmhelper.SM2Util; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchProviderException; +import java.security.cert.*; +import java.util.List; + +public class SM2CertUtil { + public static BCECPublicKey getBCECPublicKey(X509Certificate sm2Cert) { + ECPublicKey pubKey = (ECPublicKey) sm2Cert.getPublicKey(); + ECPoint q = pubKey.getQ(); + ECParameterSpec parameterSpec = new ECParameterSpec(SM2Util.CURVE, SM2Util.G_POINT, + SM2Util.SM2_ECC_N, SM2Util.SM2_ECC_H); + ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(q, parameterSpec); + return new BCECPublicKey(pubKey.getAlgorithm(), pubKeySpec, + BouncyCastleProvider.CONFIGURATION); + } + + /** + * 校验证书 + * + * @param issuerPubKey 从颁发者CA证书中提取出来的公钥 + * @param cert 待校验的证书 + * @return + */ + public static boolean verifyCertificate(BCECPublicKey issuerPubKey, X509Certificate cert) { + try { + cert.verify(issuerPubKey, BouncyCastleProvider.PROVIDER_NAME); + } catch (Exception ex) { + return false; + } + return true; + } + + public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException, + NoSuchProviderException { + InputStream is = null; + try { + is = new FileInputStream(certFilePath); + return getX509Certificate(is); + } finally { + if (is != null) { + is.close(); + } + } + } + + public static X509Certificate getX509Certificate(byte[] certBytes) throws CertificateException, + NoSuchProviderException { + ByteArrayInputStream bais = new ByteArrayInputStream(certBytes); + return getX509Certificate(bais); + } + + public static X509Certificate getX509Certificate(InputStream is) throws CertificateException, + NoSuchProviderException { + CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME); + return (X509Certificate) cf.generateCertificate(is); + } + + public static CertPath getCertificateChain(String certChainPath) throws IOException, CertificateException, + NoSuchProviderException { + InputStream is = null; + try { + is = new FileInputStream(certChainPath); + return getCertificateChain(is); + } finally { + if (is != null) { + is.close(); + } + } + } + + public static CertPath getCertificateChain(byte[] certChainBytes) throws CertificateException, + NoSuchProviderException { + ByteArrayInputStream bais = new ByteArrayInputStream(certChainBytes); + return getCertificateChain(bais); + } + + public static byte[] getCertificateChainBytes(CertPath certChain) throws CertificateEncodingException { + return certChain.getEncoded("PKCS7"); + } + + public static CertPath getCertificateChain(InputStream is) throws CertificateException, NoSuchProviderException { + CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME); + return cf.generateCertPath(is, "PKCS7"); + } + + public static CertPath getCertificateChain(List certs) throws CertificateException, + NoSuchProviderException { + CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME); + return cf.generateCertPath(certs); + } + + public static X509Certificate getX509CertificateFromPfx(byte[] pfxDER, String passwd) throws Exception { + InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray()); + PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER); + + ContentInfo[] infos = pfx.getContentInfos(); + if (infos.length != 2) { + throw new Exception("Only support one pair ContentInfo"); + } + + for (int i = 0; i != infos.length; i++) { + if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) { + PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider); + PKCS12SafeBag[] bags = dataFact.getSafeBags(); + X509CertificateHolder certHoler = (X509CertificateHolder) bags[0].getBagValue(); + return SM2CertUtil.getX509Certificate(certHoler.getEncoded()); + } + } + + throw new Exception("Not found X509Certificate in this pfx"); + } + + public static BCECPublicKey getPublicKeyFromPfx(byte[] pfxDER, String passwd) throws Exception { + return SM2CertUtil.getBCECPublicKey(getX509CertificateFromPfx(pfxDER, passwd)); + } + + public static BCECPrivateKey getPrivateKeyFromPfx(byte[] pfxDER, String passwd) throws Exception { + InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder() + .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray()); + PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER); + + ContentInfo[] infos = pfx.getContentInfos(); + if (infos.length != 2) { + throw new Exception("Only support one pair ContentInfo"); + } + + for (int i = 0; i != infos.length; i++) { + if (!infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) { + PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]); + PKCS12SafeBag[] bags = dataFact.getSafeBags(); + PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo) bags[0].getBagValue(); + PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider); + BCECPrivateKey privateKey = BCECUtil.convertPKCS8ToECPrivateKey(info.getEncoded()); + return privateKey; + } + } + + throw new Exception("Not found Private Key in this pfx"); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2PfxMaker.java b/src/main/gm/org.zz/gmhelper/cert/SM2PfxMaker.java new file mode 100644 index 0000000000000000000000000000000000000000..834a738906be46c22f7505bd65f975f797fb0ee4 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2PfxMaker.java @@ -0,0 +1,113 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.DERBMPString; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.crypto.engines.DESedeEngine; +import org.bouncycastle.crypto.engines.RC2Engine; +import org.bouncycastle.crypto.modes.CBCBlockCipher; +import org.bouncycastle.pkcs.*; +import org.bouncycastle.pkcs.bc.BcPKCS12MacCalculatorBuilder; +import org.bouncycastle.pkcs.bc.BcPKCS12PBEOutputEncryptorBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS12SafeBagBuilder; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; + +public class SM2PfxMaker { + + /** + * @param privKey 用户私钥 + * @param pubKey 用户公钥 + * @param chain X509证书数组,切记这里固定了必须是3个元素的数组,且第一个必须是叶子证书、第二个为中级CA证书、第三个为根CA证书 + * @param passwd 口令 + * @return + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws PKCSException + */ + public PKCS12PfxPdu makePfx(PrivateKey privKey, PublicKey pubKey, X509Certificate[] chain, String passwd) + throws NoSuchAlgorithmException, IOException, PKCSException { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + + PKCS12SafeBagBuilder taCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[2]); + taCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("Primary Certificate")); + + PKCS12SafeBagBuilder caCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[1]); + caCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("Intermediate Certificate")); + + PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(chain[0]); + eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("User Key")); + eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, + extUtils.createSubjectKeyIdentifier(pubKey)); + + char[] passwdChars = passwd.toCharArray(); + PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, + new BcPKCS12PBEOutputEncryptorBuilder( + PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, + new CBCBlockCipher(new DESedeEngine())).build(passwdChars)); + keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("User Key")); + keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, + extUtils.createSubjectKeyIdentifier(pubKey)); + + PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder(); + PKCS12SafeBag[] certs = new PKCS12SafeBag[3]; + certs[0] = eeCertBagBuilder.build(); + certs[1] = caCertBagBuilder.build(); + certs[2] = taCertBagBuilder.build(); + pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder( + PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, + new CBCBlockCipher(new RC2Engine())).build(passwdChars), + certs); + pfxPduBuilder.addData(keyBagBuilder.build()); + return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwdChars); + } + + /** + * @param privKey 用户私钥 + * @param pubKey 用户公钥 + * @param cert X509证书 + * @param passwd 口令 + * @return + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws PKCSException + */ + public PKCS12PfxPdu makePfx(PrivateKey privKey, PublicKey pubKey, X509Certificate cert, String passwd) + throws NoSuchAlgorithmException, IOException, PKCSException { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + + PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(cert); + eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("User Key")); + eeCertBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, + extUtils.createSubjectKeyIdentifier(pubKey)); + + char[] passwdChars = passwd.toCharArray(); + PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privKey, + new BcPKCS12PBEOutputEncryptorBuilder( + PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, + new CBCBlockCipher(new DESedeEngine())).build(passwdChars)); + keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, + new DERBMPString("User Key")); + keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, + extUtils.createSubjectKeyIdentifier(pubKey)); + + PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder(); + PKCS12SafeBag[] certs = new PKCS12SafeBag[1]; + certs[0] = eeCertBagBuilder.build(); + pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder( + PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC, + new CBCBlockCipher(new RC2Engine())).build(passwdChars), + certs); + pfxPduBuilder.addData(keyBagBuilder.build()); + return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwdChars); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2Pkcs12Maker.java b/src/main/gm/org.zz/gmhelper/cert/SM2Pkcs12Maker.java new file mode 100644 index 0000000000000000000000000000000000000000..ca04a3613b8fe9ce76f222942e0f8d8371f07d45 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2Pkcs12Maker.java @@ -0,0 +1,49 @@ +package org.zz.gmhelper.cert; + +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * @author Lijun Liao https:/github.com/xipki + */ +public class SM2Pkcs12Maker { + + /** + * @param privKey 用户私钥 + * @param chain X509证书数组, + * 第一个(index 0)为privKey对应的证书,index i+1 是index i的CA证书 + * @param passwd 口令 + * @return the PKCS#12 keystore + * @throws NoSuchProviderException + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws PKCSException + */ + public KeyStore makePkcs12(PrivateKey privKey, X509Certificate[] chain, char[] passwd) + throws KeyStoreException, NoSuchProviderException, + NoSuchAlgorithmException, CertificateException, IOException { + KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); + ks.load(null, passwd); + ks.setKeyEntry("User Key", privKey, passwd, chain); + return ks; + } + + /** + * @param privKey 用户私钥 + * @param cert X509证书 + * @param passwd 口令 + * @return the PKCS12 keystore + * @throws NoSuchAlgorithmException + * @throws IOException + * @throws PKCSException + */ + public KeyStore makePkcs12(PrivateKey privKey, X509Certificate cert, char[] passwd) + throws KeyStoreException, NoSuchProviderException, + NoSuchAlgorithmException, CertificateException, IOException { + return makePkcs12(privKey, new X509Certificate[] {cert}, passwd); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2PrivateKey.java b/src/main/gm/org.zz/gmhelper/cert/SM2PrivateKey.java new file mode 100644 index 0000000000000000000000000000000000000000..ec93b3e9f2303e0226e5c13ac656388e8a0d4334 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2PrivateKey.java @@ -0,0 +1,81 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.jcajce.provider.config.ProviderConfiguration; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.IOException; +import java.security.spec.ECParameterSpec; + +public class SM2PrivateKey extends BCECPrivateKey { + private transient DERBitString sm2PublicKey; + private boolean withCompression; + + public SM2PrivateKey(BCECPrivateKey privateKey, BCECPublicKey publicKey) { + super(privateKey.getAlgorithm(), privateKey); + this.sm2PublicKey = getSM2PublicKeyDetails(new SM2PublicKey(publicKey.getAlgorithm(), publicKey)); + this.withCompression = false; + } + + @Override + public void setPointFormat(String style) { + withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style)); + } + + /** + * Return a PKCS8 representation of the key. The sequence returned + * represents a full PrivateKeyInfo object. + * + * @return a PKCS8 representation of the key. + */ + @Override + public byte[] getEncoded() { + ECParameterSpec ecSpec = getParams(); + ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION; + ASN1Encodable params = SM2PublicKey.ID_SM2_PUBKEY_PARAM; + + int orderBitLength; + if (ecSpec == null) { + orderBitLength = ECUtil.getOrderBitLength(configuration, null, this.getS()); + } else { + orderBitLength = ECUtil.getOrderBitLength(configuration, ecSpec.getOrder(), this.getS()); + } + + PrivateKeyInfo info; + org.bouncycastle.asn1.sec.ECPrivateKey keyStructure; + + if (sm2PublicKey != null) { + keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), sm2PublicKey, params); + } else { + keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), params); + } + + try { + info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure); + + return info.getEncoded(ASN1Encoding.DER); + } catch (IOException e) { + return null; + } + } + + private DERBitString getSM2PublicKeyDetails(SM2PublicKey pub) { + try { + SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded())); + + return info.getPublicKeyData(); + } catch (IOException e) { // should never happen + return null; + } + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2PublicKey.java b/src/main/gm/org.zz/gmhelper/cert/SM2PublicKey.java new file mode 100644 index 0000000000000000000000000000000000000000..87e1d3f1d37ba503c6d3775233960813811fe4d2 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2PublicKey.java @@ -0,0 +1,44 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ECPoint; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil; + +public class SM2PublicKey extends BCECPublicKey { + public static final ASN1ObjectIdentifier ID_SM2_PUBKEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301"); + + private boolean withCompression; + + public SM2PublicKey(BCECPublicKey key) { + super(key.getAlgorithm(), key); + this.withCompression = false; + } + + public SM2PublicKey(String algorithm, BCECPublicKey key) { + super(algorithm, key); + this.withCompression = false; + } + + @Override + public byte[] getEncoded() { + ASN1OctetString p = ASN1OctetString.getInstance( + new X9ECPoint(getQ(), withCompression).toASN1Primitive()); + + // stored curve is null if ImplicitlyCa + SubjectPublicKeyInfo info = new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, ID_SM2_PUBKEY_PARAM), + p.getOctets()); + + return KeyUtil.getEncodedSubjectPublicKeyInfo(info); + } + + @Override + public void setPointFormat(String style) { + withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style)); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/SM2X509CertMaker.java b/src/main/gm/org.zz/gmhelper/cert/SM2X509CertMaker.java new file mode 100644 index 0000000000000000000000000000000000000000..943fac8324104b277568fb7be4fd576595b48086 --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/SM2X509CertMaker.java @@ -0,0 +1,273 @@ +package org.zz.gmhelper.cert; + +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.x500.AttributeTypeAndValue; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.asn1.x509.*; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class SM2X509CertMaker { + + private static enum CertLevel { + RootCA, + SubCA, + EndEntity + } // class CertLevel + + public static final String SIGN_ALGO_SM3WITHSM2 = "SM3withSM2"; + + private long certExpire; + private X500Name issuerDN; + private CertSNAllocator snAllocator; + private KeyPair issuerKeyPair; + + /** + * @param issuerKeyPair 证书颁发者的密钥对。 + * 其实一般的CA的私钥都是要严格保护的。 + * 一般CA的私钥都会放在加密卡/加密机里,证书的签名由加密卡/加密机完成。 + * 这里仅是为了演示BC库签发证书的用法,所以暂时不作太多要求。 + * @param certExpire 证书有效时间,单位毫秒 + * @param issuer 证书颁发者信息 + * @param snAllocator 维护/分配证书序列号的实例,证书序列号应该递增且不重复 + */ + public SM2X509CertMaker(KeyPair issuerKeyPair, long certExpire, X500Name issuer, + CertSNAllocator snAllocator) { + this.issuerKeyPair = issuerKeyPair; + this.certExpire = certExpire; + this.issuerDN = issuer; + this.snAllocator = snAllocator; + } + + /** + * 生成根CA证书 + * + * @param csr CSR + * @return 新的证书 + * @throws Exception 如果错误发生 + */ + public X509Certificate makeRootCACert(byte[] csr) + throws Exception { + KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign); + return makeCertificate(CertLevel.RootCA, null, csr, usage, null); + } + + /** + * 生成SubCA证书 + * + * @param csr CSR + * @return 新的证书 + * @throws Exception 如果错误发生 + */ + public X509Certificate makeSubCACert(byte[] csr) + throws Exception { + KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign); + return makeCertificate(CertLevel.SubCA, 0, csr, usage, null); + } + + /** + * 生成SSL用户证书 + * + * @param csr CSR + * @return 新的证书 + * @throws Exception 如果错误发生 + */ + public X509Certificate makeSSLEndEntityCert(byte[] csr) + throws Exception { + return makeEndEntityCert(csr, + new KeyPurposeId[] {KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}); + } + + /** + * 生成用户证书 + * + * @param csr CSR + * @param extendedKeyUsages 扩展指数用途。 + * @return 新的证书 + * @throws Exception 如果错误发生 + */ + public X509Certificate makeEndEntityCert(byte[] csr, + KeyPurposeId[] extendedKeyUsages) + throws Exception { + KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement + | KeyUsage.dataEncipherment | KeyUsage.keyEncipherment); + return makeCertificate(CertLevel.SubCA, null, csr, usage, extendedKeyUsages); + } + + /** + * @param isCA 是否是颁发给CA的证书 + * @param keyUsage 证书用途 + * @param csr CSR + * @return + * @throws Exception + */ + private X509Certificate makeCertificate(CertLevel certLevel, Integer pathLenConstrain, + byte[] csr, KeyUsage keyUsage, KeyPurposeId[] extendedKeyUsages) + throws Exception { + if (certLevel == CertLevel.EndEntity) { + if (keyUsage.hasUsages(KeyUsage.keyCertSign)) { + throw new IllegalArgumentException( + "keyusage keyCertSign is not allowed in EndEntity Certificate"); + } + } + + PKCS10CertificationRequest request = new PKCS10CertificationRequest(csr); + SubjectPublicKeyInfo subPub = request.getSubjectPublicKeyInfo(); + + PrivateKey issPriv = issuerKeyPair.getPrivate(); + PublicKey issPub = issuerKeyPair.getPublic(); + + X500Name subject = request.getSubject(); + String email = null; + String commonName = null; + /* + * RFC 5280 §4.2.1.6 Subject + * Conforming implementations generating new certificates with + * electronic mail addresses MUST use the rfc822Name in the subject + * alternative name extension (Section 4.2.1.6) to describe such + * identities. Simultaneous inclusion of the emailAddress attribute in + * the subject distinguished name to support legacy implementations is + * deprecated but permitted. + */ + RDN[] rdns = subject.getRDNs(); + List newRdns = new ArrayList<>(rdns.length); + for (int i = 0; i < rdns.length; i++) { + RDN rdn = rdns[i]; + + AttributeTypeAndValue atv = rdn.getFirst(); + ASN1ObjectIdentifier type = atv.getType(); + if (BCStyle.EmailAddress.equals(type)) { + email = IETFUtils.valueToString(atv.getValue()); + } else { + if (BCStyle.CN.equals(type)) { + commonName = IETFUtils.valueToString(atv.getValue()); + } + newRdns.add(rdn); + } + } + + List subjectAltNames = new LinkedList<>(); + if (email != null) { + subject = new X500Name(newRdns.toArray(new RDN[0])); + subjectAltNames.add( + new GeneralName(GeneralName.rfc822Name, + new DERIA5String(email, true))); + } + + boolean selfSignedEECert = false; + switch (certLevel) { + case RootCA: + if (issuerDN.equals(subject)) { + subject = issuerDN; + } else { + throw new IllegalArgumentException("subject != issuer for certLevel " + CertLevel.RootCA); + } + break; + case SubCA: + if (issuerDN.equals(subject)) { + throw new IllegalArgumentException( + "subject MUST not equals issuer for certLevel " + certLevel); + } + break; + default: + if (issuerDN.equals(subject)) { + selfSignedEECert = true; + subject = issuerDN; + } + } + + BigInteger serialNumber = snAllocator.nextSerialNumber(); + Date notBefore = new Date(); + Date notAfter = new Date(notBefore.getTime() + certExpire); + X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder( + issuerDN, serialNumber, + notBefore, notAfter, + subject, subPub); + + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + v3CertGen.addExtension(Extension.subjectKeyIdentifier, false, + extUtils.createSubjectKeyIdentifier(subPub)); + if (certLevel != CertLevel.RootCA && !selfSignedEECert) { + v3CertGen.addExtension(Extension.authorityKeyIdentifier, false, + extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(issPub.getEncoded()))); + } + + // RFC 5280 §4.2.1.9 Basic Constraints: + // Conforming CAs MUST include this extension in all CA certificates + // that contain public keys used to validate digital signatures on + // certificates and MUST mark the extension as critical in such + // certificates. + BasicConstraints basicConstraints; + if (certLevel == CertLevel.EndEntity) { + basicConstraints = new BasicConstraints(false); + } else { + basicConstraints = pathLenConstrain == null + ? new BasicConstraints(true) : new BasicConstraints(pathLenConstrain.intValue()); + } + v3CertGen.addExtension(Extension.basicConstraints, true, basicConstraints); + + // RFC 5280 §4.2.1.3 Key Usage: When present, conforming CAs SHOULD mark this extension as critical. + v3CertGen.addExtension(Extension.keyUsage, true, keyUsage); + + if (extendedKeyUsages != null) { + ExtendedKeyUsage xku = new ExtendedKeyUsage(extendedKeyUsages); + v3CertGen.addExtension(Extension.extendedKeyUsage, false, xku); + + boolean forSSLServer = false; + for (KeyPurposeId purposeId : extendedKeyUsages) { + if (KeyPurposeId.id_kp_serverAuth.equals(purposeId)) { + forSSLServer = true; + break; + } + } + + if (forSSLServer) { + if (commonName == null) { + throw new IllegalArgumentException("commonName must not be null"); + } + GeneralName name = new GeneralName(GeneralName.dNSName, + new DERIA5String(commonName, true)); + subjectAltNames.add(name); + } + } + + if (!subjectAltNames.isEmpty()) { + v3CertGen.addExtension(Extension.subjectAlternativeName, false, + new GeneralNames(subjectAltNames.toArray(new GeneralName[0]))); + } + + JcaContentSignerBuilder contentSignerBuilder = makeContentSignerBuilder(issPub); + X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME) + .getCertificate(v3CertGen.build(contentSignerBuilder.build(issPriv))); + cert.verify(issPub); + + return cert; + } + + private JcaContentSignerBuilder makeContentSignerBuilder(PublicKey issPub) throws Exception { + if (issPub.getAlgorithm().equals("EC")) { + JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGN_ALGO_SM3WITHSM2); + contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME); + return contentSignerBuilder; + } + throw new Exception("Unsupported PublicKey Algorithm:" + issPub.getAlgorithm()); + } +} diff --git a/src/main/gm/org.zz/gmhelper/cert/exception/InvalidX500NameException.java b/src/main/gm/org.zz/gmhelper/cert/exception/InvalidX500NameException.java new file mode 100644 index 0000000000000000000000000000000000000000..d04d176582b5801a3d838e2d7373f4debbe5917b --- /dev/null +++ b/src/main/gm/org.zz/gmhelper/cert/exception/InvalidX500NameException.java @@ -0,0 +1,21 @@ +package org.zz.gmhelper.cert.exception; + +public class InvalidX500NameException extends Exception { + private static final long serialVersionUID = 3192247087539921768L; + + public InvalidX500NameException() { + super(); + } + + public InvalidX500NameException(String message) { + super(message); + } + + public InvalidX500NameException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidX500NameException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/gm/org/paillier/CommonUtils.java b/src/main/gm/org/paillier/CommonUtils.java new file mode 100755 index 0000000000000000000000000000000000000000..fbb0f39d6d15ed93a7813a84fa7edb3f5214a697 --- /dev/null +++ b/src/main/gm/org/paillier/CommonUtils.java @@ -0,0 +1,109 @@ +package org.paillier; + +import java.math.BigInteger; + +public class CommonUtils { + public CommonUtils() {} + + public static byte[] intToByte4(int i) { + byte[] targets = new byte[4]; + targets[3] = (byte) (i & 0xFF); + targets[2] = (byte) (i >> 8 & 0xFF); + targets[1] = (byte) (i >> 16 & 0xFF); + targets[0] = (byte) (i >> 24 & 0xFF); + return targets; + } + + public static byte[] longToByte8(long lo) { + byte[] targets = new byte[8]; + for (int i = 0; i < 8; i++) { + int offset = (targets.length - 1 - i) * 8; + targets[i] = (byte) ((lo >>> offset) & 0xFF); + } + return targets; + } + + public static byte[] unsignedShortToByte2(int s) { + byte[] targets = new byte[2]; + targets[0] = (byte) (s >> 8 & 0xFF); + targets[1] = (byte) (s & 0xFF); + return targets; + } + + public static int byte2ToUnsignedShort(byte[] bytes) { + return byte2ToUnsignedShort(bytes, 0); + } + + public static int byte2ToUnsignedShort(byte[] bytes, int off) { + int high = bytes[off]; + int low = bytes[off + 1]; + return (high << 8 & 0xFF00) | (low & 0xFF); + } + + public static int byte4ToInt(byte[] bytes, int off) { + int b0 = bytes[off] & 0xFF; + int b1 = bytes[off + 1] & 0xFF; + int b2 = bytes[off + 2] & 0xFF; + int b3 = bytes[off + 3] & 0xFF; + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + public static byte[] asUnsignedByteArray(BigInteger paramBigInteger) { + byte[] arrayOfByte1 = paramBigInteger.toByteArray(); + if (arrayOfByte1[0] == 0) { + byte[] arrayOfByte2 = new byte[arrayOfByte1.length - 1]; + System.arraycopy(arrayOfByte1, 1, arrayOfByte2, 0, arrayOfByte2.length); + return arrayOfByte2; + } + return arrayOfByte1; + } + + public static byte[] asUnsignedByteArray(BigInteger paramBigInteger, int byteLength) { + byte[] arrayOfByte1 = asUnsignedByteArray(paramBigInteger); + if (arrayOfByte1.length < byteLength) { + byte[] arrayOfByte2 = new byte[byteLength]; + int offset = byteLength - arrayOfByte1.length; + for (int i = 0; i < offset; i++) { + arrayOfByte2[i] = 0; + } + System.arraycopy(arrayOfByte1, 0, arrayOfByte2, offset, arrayOfByte1.length); + return arrayOfByte2; + } + return arrayOfByte1; + } + + public static BigInteger fromUnsignedByteArray(byte[] paramArrayOfByte) { + return new BigInteger(1, paramArrayOfByte); + } + + public static String byteToHexString(byte[] bt) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < bt.length; i++) { + String hex = Integer.toHexString(bt[i] & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + sb.append(hex.toUpperCase()); + } + return sb.toString(); + } + + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | (charToByte(hexChars[pos + 1]) & 0xff)); + } + return d; + } + + private static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } +} diff --git a/src/main/gm/org/paillier/PaillierCipher.java b/src/main/gm/org/paillier/PaillierCipher.java new file mode 100755 index 0000000000000000000000000000000000000000..3ee4773419fc1d00cb5747439fb34f5bae4d9680 --- /dev/null +++ b/src/main/gm/org/paillier/PaillierCipher.java @@ -0,0 +1,129 @@ +package org.paillier; + +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Random; + +public class PaillierCipher { + + public static String encrypt(BigInteger m, PublicKey publicKey) { + return CommonUtils.byteToHexString(encryptAsBytes(m, publicKey)); + } + + public static byte[] encryptAsBytes(BigInteger m, PublicKey publicKey) { + RSAPublicKey rsaPubKey = (RSAPublicKey) publicKey; + BigInteger n = rsaPubKey.getModulus(); + BigInteger g = n.add(BigInteger.ONE); + + BigInteger random; + do { + random = new BigInteger(n.bitLength(), new Random()); + } while (random.signum() != 1); + + if (m.signum() == -1) { + m = m.mod(n); + } + + BigInteger nsquare = n.multiply(n); + BigInteger ciphertext = + g.modPow(m, nsquare).multiply(random.modPow(n, nsquare)).mod(nsquare); + + byte[] nBytes = CommonUtils.asUnsignedByteArray(n); + byte[] nLenBytes = CommonUtils.unsignedShortToByte2(nBytes.length); + byte[] cipherBytes = CommonUtils.asUnsignedByteArray(ciphertext, n.bitLength() / 4); + byte[] data = new byte[nLenBytes.length + nBytes.length + cipherBytes.length]; + System.arraycopy(nLenBytes, 0, data, 0, nLenBytes.length); + System.arraycopy(nBytes, 0, data, nLenBytes.length, nBytes.length); + System.arraycopy( + cipherBytes, 0, data, nLenBytes.length + nBytes.length, cipherBytes.length); + return data; + } + + public static BigInteger decrypt(String ciphertext, PrivateKey privateKey) { + return decrypt(CommonUtils.hexStringToBytes(ciphertext), privateKey); + } + + public static BigInteger decrypt(byte[] ciphertext, PrivateKey privateKey) { + RSAPrivateCrtKey rsaPriKey = (RSAPrivateCrtKey) privateKey; + BigInteger n = rsaPriKey.getModulus(); + BigInteger lambda = + rsaPriKey + .getPrimeP() + .subtract(BigInteger.ONE) + .multiply(rsaPriKey.getPrimeQ().subtract(BigInteger.ONE)); + + int nLen = CommonUtils.byte2ToUnsignedShort(ciphertext); + byte[] nBytes = new byte[nLen]; + System.arraycopy(ciphertext, 2, nBytes, 0, nLen); + BigInteger n1 = CommonUtils.fromUnsignedByteArray(nBytes); + if (n1.compareTo(n) != 0) { + System.err.println("Invalid ciphertext, cannot match n parameter"); + return null; + } + + byte[] data = new byte[ciphertext.length - nLen - 2]; + System.arraycopy(ciphertext, 2 + nLen, data, 0, ciphertext.length - nLen - 2); + BigInteger intCiphertext = CommonUtils.fromUnsignedByteArray(data); + + BigInteger mu = lambda.modInverse(n); + BigInteger nsquare = n.multiply(n); + BigInteger message = + intCiphertext + .modPow(lambda, nsquare) + .subtract(BigInteger.ONE) + .divide(n) + .multiply(mu) + .mod(n); + BigInteger maxValue = BigInteger.ONE.shiftLeft(n.bitLength() / 2); + if (message.compareTo(maxValue) > 0) { + return message.subtract(n); + } else { + return message; + } + } + + public static String ciphertextAdd(String ciphertext1, String ciphertext2) { + return CommonUtils.byteToHexString( + ciphertextAdd( + CommonUtils.hexStringToBytes(ciphertext1), + CommonUtils.hexStringToBytes(ciphertext2))); + } + + public static byte[] ciphertextAdd(byte[] ciphertext1, byte[] ciphertext2) { + int nLen1 = CommonUtils.byte2ToUnsignedShort(ciphertext1); + byte[] nBytes1 = new byte[nLen1]; + System.arraycopy(ciphertext1, 2, nBytes1, 0, nLen1); + BigInteger n1 = CommonUtils.fromUnsignedByteArray(nBytes1); + byte[] data1 = new byte[ciphertext1.length - nLen1 - 2]; + System.arraycopy(ciphertext1, 2 + nLen1, data1, 0, ciphertext1.length - nLen1 - 2); + + int nLen2 = CommonUtils.byte2ToUnsignedShort(ciphertext2); + byte[] nBytes2 = new byte[nLen2]; + System.arraycopy(ciphertext2, 2, nBytes2, 0, nLen2); + BigInteger n2 = CommonUtils.fromUnsignedByteArray(nBytes2); + if (n2.compareTo(n1) != 0) { + System.err.println("ciphertext1 cannot match ciphertext2"); + return null; + } + + byte[] data2 = new byte[ciphertext2.length - nLen2 - 2]; + System.arraycopy(ciphertext2, 2 + nLen2, data2, 0, ciphertext2.length - nLen2 - 2); + + BigInteger ct1 = CommonUtils.fromUnsignedByteArray(data1); + BigInteger ct2 = CommonUtils.fromUnsignedByteArray(data2); + BigInteger nsquare = n1.multiply(n1); + BigInteger ct = ct1.multiply(ct2).mod(nsquare); + + byte[] nLenBytes = CommonUtils.unsignedShortToByte2(nBytes1.length); + byte[] cipherBytes = CommonUtils.asUnsignedByteArray(ct, n1.bitLength() / 4); + byte[] data = new byte[nLenBytes.length + nBytes1.length + cipherBytes.length]; + System.arraycopy(nLenBytes, 0, data, 0, nLenBytes.length); + System.arraycopy(nBytes1, 0, data, nLenBytes.length, nBytes1.length); + System.arraycopy( + cipherBytes, 0, data, nLenBytes.length + nBytes1.length, cipherBytes.length); + return data; + } +} diff --git a/src/main/gm/org/paillier/PaillierKeyPair.java b/src/main/gm/org/paillier/PaillierKeyPair.java new file mode 100755 index 0000000000000000000000000000000000000000..54e0287045254b50310d3c0d53cc59faeb96d9d0 --- /dev/null +++ b/src/main/gm/org/paillier/PaillierKeyPair.java @@ -0,0 +1,113 @@ +package org.paillier; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemObjectGenerator; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +public class PaillierKeyPair { + + public static KeyPair generateGoodKeyPair() { + return generateKeyPair(2048); + } + + public static KeyPair generateStrongKeyPair() { + return generateKeyPair(4096); + } + + private static KeyPair generateKeyPair(int len) { + if (len < 2048) { + return null; + } + + KeyPairGenerator generator; + try { + generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(len, new SecureRandom()); + return generator.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return null; + } + + public static String publicKeyToPem(PublicKey publicKey) { + StringWriter pemStrWriter = new StringWriter(); + PemWriter pemWriter = null; + try { + pemWriter = new PemWriter(pemStrWriter); + PemObject pemObject = new PemObject("PUBLIC KEY", publicKey.getEncoded()); + pemWriter.writeObject(pemObject); + pemWriter.flush(); + pemWriter.close(); + return pemStrWriter.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static PublicKey pemToPublicKey(String publicKeyStr) { + try { + StringReader pemStrReader = new StringReader(publicKeyStr); + PemReader pemReader = new PemReader(pemStrReader); + byte[] pubKey = pemReader.readPemObject().getContent(); + pemReader.close(); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(new X509EncodedKeySpec(pubKey)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + return null; + } + + public static String privateKeyToPem(PrivateKey privateKey) { + try { + StringWriter pemStrWriter = new StringWriter(); + PemWriter pemWriter = new PemWriter(pemStrWriter); + PemObjectGenerator pemObjectGenerator = + new PemObject("PRIVATE KEY", privateKey.getEncoded()); + pemWriter.writeObject(pemObjectGenerator); + pemWriter.flush(); + pemWriter.close(); + return pemStrWriter.toString(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public static PrivateKey pemToPrivateKey(String privateKeyStr) { + try { + StringReader pemStrReader = new StringReader(privateKeyStr); + PemReader pemReader = new PemReader(pemStrReader); + byte[] priKey = pemReader.readPemObject().getContent(); + pemReader.close(); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(new PKCS8EncodedKeySpec(priKey)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/doip/functionalTest/integratedTest/IntegratedTestClient.java b/src/main/java/doip/functionalTest/integratedTest/IntegratedTestClient.java index 4773538dd9328cd41ceaf199a7dbab9a3473177f..e8a0dbdcca4bf8bf714a4e90721ff9591034a705 100644 --- a/src/main/java/doip/functionalTest/integratedTest/IntegratedTestClient.java +++ b/src/main/java/doip/functionalTest/integratedTest/IntegratedTestClient.java @@ -55,6 +55,7 @@ public class IntegratedTestClient { JSONObject jsonObject = new JSONObject(content); serviceID = jsonObject.getString("serviceID"); serviceAddr = jsonObject.getString("serviceAddr"); + out = new PrintStream("./log/doipClient.log"); // System.setOut(out); doipClient.connect(ClientConfig.fromUrl(serviceAddr)); @@ -276,7 +277,7 @@ public class IntegratedTestClient { if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { LOGGER.warn("----- Update Time Out -----"); } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { - LOGGER.error("----- Do Not Found -----"); + LOGGER.error("- [FAIL] Proxy & Server Create ------ Do Not Found -----"); } } else { String[] flagData = {logo + "", "0.DOIP/Op.Update", "Pass", msg.header.parameters.response.toString()}; // 不通过 diff --git a/src/main/java/doip/functionalTest/unitTest/UnitTestSuite.java b/src/main/java/doip/functionalTest/unitTest/UnitTestSuite.java deleted file mode 100644 index 1b84f7bbf261a886186d552915fed3c190dde886..0000000000000000000000000000000000000000 --- a/src/main/java/doip/functionalTest/unitTest/UnitTestSuite.java +++ /dev/null @@ -1,12 +0,0 @@ -package doip.functionalTest.unitTest; - -import org.junit.runner.JUnitCore; -import org.junit.runner.Result; - -public class UnitTestSuite { - public static void main(String[] args) { - Result result = JUnitCore.runClasses(UnitTestClient.class); - System.out.println(result.wasSuccessful()?"Mission Success":"Mission Failed"); - System.exit(result.wasSuccessful() ? 0 : 1); - } -} \ No newline at end of file diff --git a/src/main/java/doip/safetyTest/encryptTest/encryptCient.java b/src/main/java/doip/safetyTest/encryptTest/encryptCient.java new file mode 100644 index 0000000000000000000000000000000000000000..2d40a3b14bf9531890717d6a63066ec1306fad5b --- /dev/null +++ b/src/main/java/doip/safetyTest/encryptTest/encryptCient.java @@ -0,0 +1,20 @@ +package doip.safetyTest.encryptTest; + +import java.io.IOException; + +public class encryptCient { + public static void main(String[] args) throws IOException, InterruptedException { + encryptSuite client = new encryptSuite(); + client.config(); + client.testHelloReconnect(); + client.testCreateReconnect(); + client.testListOpsReconnect(); + client.testRetrieveReconnect(); + client.testSearchReconnect(); + client.testUpdateReconnect(); + client.testRetrieveReconnect(); + client.testDeleteReconnect(); + client.testRetrieveReconnect(); + client.checkTest(); + } +} diff --git a/src/main/java/doip/safetyTest/encryptTest/encryptSuite.java b/src/main/java/doip/safetyTest/encryptTest/encryptSuite.java new file mode 100644 index 0000000000000000000000000000000000000000..c85df0be42c2b6086b44e01c9a0b17d20f305262 --- /dev/null +++ b/src/main/java/doip/safetyTest/encryptTest/encryptSuite.java @@ -0,0 +1,550 @@ +package doip.safetyTest.encryptTest; + +import com.github.openjson.JSONObject; +import com.google.gson.Gson; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.tools.picocli.CommandLine; +import org.bdware.doip.codec.digitalObject.DigitalObject; +import org.bdware.doip.codec.digitalObject.DoType; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.DoipResponseCode; +import org.bdware.doip.codec.exception.DoDecodeException; +import org.bdware.doip.codec.metadata.SearchParameter; +import org.bdware.doip.encrypt.JWKCryptoManager; +import org.bdware.doip.encrypt.SM2Signer; +import org.bdware.doip.endpoint.client.ClientConfig; +import org.bdware.doip.endpoint.client.DoipClientImpl; +import org.bdware.doip.endpoint.client.DoipMessageCallback; +import doip.safetyTest.jwk.TestConstants; +import doip.safetyTest.jwk.TestClientKeyRetriever; + +import org.junit.Test; +import org.springframework.aop.scope.ScopedProxyUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class encryptSuite { + static Logger LOGGER = LogManager.getLogger(encryptSuite.class); + int totalCount = 1; + static Scanner input = new Scanner(System.in); + static String serviceID = null; + final static DoipClientImpl doipClient = new DoipClientImpl(); + static String serviceAddr = null; + static DigitalObject saveDo; + static File file; + public static List outData = new ArrayList<>(10); +// static FileHandler fh; + + // public static Logger LOGGER = LogManager.getLogger(SecretTest.class); + public static PrintStream out = null; + public static int answer = 0; + public static boolean flag = true; + public static int logo = 0; + + + public void config() throws IOException { + String[] flagData = {"Number", "Function", "Result", "Details"}; // 不通过 + outData.add(flagData); + file = new File("./config/clientConf.json"); + String content = FileUtils.readFileToString(file, "UTF-8"); + JSONObject jsonObject = new JSONObject(content); + serviceID = jsonObject.getString("serviceID"); + serviceAddr = jsonObject.getString("serviceAddr"); + + out = new PrintStream("./log/doipClient.log"); + } + + public void testHelloReconnect() throws InterruptedException { + LOGGER.info("----- Hello Test -----"); + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + + doipClient.hello(serviceID, msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Hello not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Hello", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Hello Time Out -----"); + } + } else { + answer++; + String[] flagData = {logo + "", "0.DOIP/Op.Hello", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + correct.incrementAndGet(); + + DigitalObject retDo; + try { + retDo = msg.body.getDataAsDigitalObject(); + LOGGER.info(new Gson().toJson(retDo)); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert retDo.id.equals(serviceID); + LOGGER.info("----- Hello Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testCreateReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + DigitalObject dobj; + dobj = new DigitalObject("aibd", DoType.DO); + saveDo = dobj; + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.create(serviceID, dobj, msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Create", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Create not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Create", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Create Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoAlreadyExist) { + LOGGER.warn("----- Do Already Exists -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Create", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + try { + //byte[] str =msg.body.encodedData; + retDo = msg.body.getDataAsDigitalObject(); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert retDo != null; + LOGGER.info("----- Create Assert Pass -----"); + LOGGER.info(retDo); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testListOpsReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.listOperations("aibd", msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- ListOperations not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- ListOperations Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + String Jstr; + String newJstr; + String[] listArray; + try { + Jstr = new String(msg.body.encodedData, "UTF-8"); + newJstr = Jstr.substring(1, Jstr.length() - 1); + LOGGER.info(newJstr); + listArray = newJstr.split(","); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + assert Arrays.asList(listArray).contains("0.DOIP/Op.Create"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Retrieve"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Update"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Delete"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.ListOperations"); + LOGGER.info("----- ListOps Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testUpdateReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + DigitalObject dobj = new DigitalObject("aibd", DoType.DOIPServiceInfo); + dobj.addAttribute("syw", "aibd"); + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.update(dobj, msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- Update not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.warn("----- Update Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("------ Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + String jsonRetDo; + try { + //byte[] str =msg.body.encodedData; + retDo = msg.body.getDataAsDigitalObject(); + jsonRetDo = new Gson().toJson(retDo); + LOGGER.info(new Gson().toJson(retDo)); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert saveDo.equals(retDo); + LOGGER.info("----- Update Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testRetrieveReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.retrieve("aibd", null, false, msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- Retrieve not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Retrieve Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + + DigitalObject retDo; + try { + retDo = msg.body.getDataAsDigitalObject(); + assert retDo.id.toString() == "aibd"; + assert saveDo.equals(retDo); + LOGGER.info("----- Retrieve Assert Pass -----"); + LOGGER.info(retDo.toString()); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testSearchReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + String query = "aibd"; + SearchParameter sp = new SearchParameter(query, 10, 0, "", "DO"); + sp.query = query; + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.search(query, sp, msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + System.out.println("response : "+msg.header.parameters.response); + LOGGER.warn("----- Search not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Search Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + try { + String s_new = new String(msg.body.encodedData, "UTF-8"); + LOGGER.info("Search Result : " + s_new); + LOGGER.info("----- Search Assert Pass -----"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testDeleteReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2)) + .setCryptoManager(new JWKCryptoManager(TestConstants.clientKey, new TestClientKeyRetriever()))); + doipClient.delete("aibd", msg -> { + assert isSignature(msg) == true; + assert isCertificate(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Delete not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Delete Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + //LOGGER.info("Retrieved:" + str + //+ " respCode:" + msg.header.parameters.response); + + if (msg.header.parameters.response.toString() == "Success") { + assert msg.header.parameters.response.toString() == "Success"; + } else { + assert msg.header.parameters.response == DoipResponseCode.DoNotFound; + } + LOGGER.info("----- Delete Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public String escapeSpecialCharacters(String data) { + String escapedData = data.replaceAll("\\R", " "); + if (data.contains(",") || data.contains("\"") || data.contains("'")) { + data = data.replace("\"", "\"\""); + escapedData = "\"" + data + "\""; + } + return escapedData; + } + + public String convertToCSV(String[] data) { + return Stream.of(data) + .map(this::escapeSpecialCharacters) + .collect(Collectors.joining(",")); + } + public void wirterCsv(String csvFileName, List data) + throws FileNotFoundException, UnsupportedEncodingException { + File csvOutputFile = new File(csvFileName); + try (PrintWriter pw = new PrintWriter(csvOutputFile, "GBK")) { + data.stream() + .map(this::convertToCSV) + .forEach(pw::println); + } + } + + public void checkTest() throws FileNotFoundException, UnsupportedEncodingException { + wirterCsv("./log/EncryptionTest.csv",outData); + if(answer == 8){ + LOGGER.info("----- All Encryption Tests Pass! -----"); + }else{ + LOGGER.info("----- Not Pass , Please Check! -----"); + } + } + boolean isCertificate(DoipMessage response){ + return ((response.header.getFlag() >> 30) == 1); + } + + boolean isSignature(DoipMessage response){ + return response.credential != null; + } +} diff --git a/src/main/java/doip/safetyTest/jwk/TestClientKeyRetriever.java b/src/main/java/doip/safetyTest/jwk/TestClientKeyRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..d422547e36759c1c87fa3ee6a4e90b7d8dc65601 --- /dev/null +++ b/src/main/java/doip/safetyTest/jwk/TestClientKeyRetriever.java @@ -0,0 +1,12 @@ +package doip.safetyTest.jwk; + +import com.nimbusds.jose.jwk.JWK; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.encrypt.KeyRetriever; + +public class TestClientKeyRetriever implements KeyRetriever { + @Override + public JWK retriveKey(DoipMessage message) { + return TestConstants.serverKey.toPublicJWK(); + } +} diff --git a/src/main/java/doip/safetyTest/jwk/TestConstants.java b/src/main/java/doip/safetyTest/jwk/TestConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..46b0a8c68b58b85d8d570ca801e3b813d187e2aa --- /dev/null +++ b/src/main/java/doip/safetyTest/jwk/TestConstants.java @@ -0,0 +1,39 @@ +package doip.safetyTest.jwk; + +import com.nimbusds.jose.jwk.JWK; +import org.junit.Test; +import org.zz.gmhelper.SM2KeyPair; + +import java.text.ParseException; + +public class TestConstants { + public static JWK serverKey = getServerKey(); + public static JWK clientKey = getClientKey(); + public static SM2KeyPair clientSM2 = SM2KeyPair.fromJson("{\"publicKey\":\"0404f1607dc48c7677f451387c4b4d1d9473cb984286f63d3a20530a731e9e25b4ac92d825df1afc11e38483e231c1730845304a961589d625ad4da3eee6061ee9\",\"privateKey\":\"56281c4fda9190e92363eeebb5dddda47b4ee0f15ea598af14704e3521ede9ee\"}"); + public static SM2KeyPair serverSM2 = SM2KeyPair.fromJson("{\"publicKey\":\"048387b21334cd8618a3821b6d57b1564af67861483d39a3a00ef8dc31c737c24f3c3a5f290bd2863c300a443428fae4a999a0a404e10d591a6cb3ab8a4041c062\",\"privateKey\":\"75e21a09547619934f4db238848ceda599ac619219018ac63663b78fd35daf2f\"}"); + + private static JWK getServerKey() { + String str = "{\"p\":\"_a6B9kW7DzBrs6P2HciO8OL6PThg-xe6vUyPgujdlP8gLFtr8FHkr5qg69-vN_hPbSloGDFlZKA4E9a00z1ki4bApH2KAaTUT6pUvxdbNv-JwOs2-2j8Ytbwdi8QhaziPJuSqafCYrVPCebUQE-ty2yBdjEMPMZ4AM1BjNBIKAU\",\"kty\":\"RSA\",\"q\":\"kTiiKZmzIn4d-iwzoKeV1hKehESoRZnXq0Me6aULoUNaF1Fz7KFicvP0Z_0fdnxJafUtLd_5z4TbWg8pzssz8936CHs2oe5U0cRGY3rJskrp0PuBfp67UIOPTBM6Ld9ZTYMpihrqJH8jBe13DlgoeZtU-9op8Xhi9p3cjCyZl3U\",\"d\":\"Sz5TPzXpmA-UQfQBEIU-P6ycqitYavWxn6RC_QMA2b4jYAYeUr6HK7eZ9JOcZEAyxOQKwhKbGZsTHu9IVgsnmAbs6Ypy_pQkSPzziVSszMetVJY1zvEVuEBlP1WHoH11mWiOoizoQ-kYlSIkL_qC47Kcc3yHdBPAGwehOYV8YOSl-XiX2IgsDIJpLJqLyGfbyFfOmpCUZbngaCl_msQQ9OiQXJ12fyIwTF1FOJwJ4e3mSPvo4WmeCsbsO6st-JRDaEFDOdlfD-KHQh65KDaZpkK3UaFjqkKJo16BgNaw1DeYxIBHLrrzRgOq2ee2Aa4dKKup94nDGqr6EYu_pWew8Q\",\"e\":\"AQAB\",\"use\":\"enc\",\"kid\":\"dcaf1347-8c74-483d-ac74-2c0bbb011741\",\"qi\":\"RhMzkITiYX87wfL5ODkOWL3Y6LaGK6gc222AlOF-GSYRKxoUvsCEpXGv4jKHBEeUXv6rxLuLa_wI2zNqL01RCkgRmYs3hx7CtAbHs4XIIYfZ3sMU-DAiJiF_T0DChDO00ySSUAJ6a_J71Ud0BMixVI7MFVj9ylgOxJDJol9WuF4\",\"dp\":\"Y1BDTt_DuNGTCJQDEWvoEgQ6RWdiCEsk72EeufhibydmOBdebYoSBnF52H4MwdOzfJ_-QaJs-HUFHzcqOZzKVRlfJ8aCFdyqxbmATgNd0W0_R8iOEOTsEeHl587LIBorw-CADW1A25XxqIW2yKqo9n-3O0c-bDii2GWC6RbNeOU\",\"dq\":\"Dhc3rN-sAQHJuNeHHuSD5mSiGuVqim5V_dkia7tG-JvHZxHRNLmoCs1e_qQR5HZEzVIr0xKzc45JlmB4RwdygAwe0ana8DVm53-q8MYeQf2A2HU-6GFQfYx2YARRldfhG2NJqYvZAjeP12hmL-8f5kTLJzDQ9wweVh2VI8jEEm0\",\"n\":\"j-f2epLdq_Z_5Ulc4z9V0sN7Zm5zreW450dId-Ag92xqlC-XWzOlBepFywOlPo6UTWr8ZHoNA_-G_SvuFILHt34qXNOin5VmIESV2a05ofoN2kyX0GsEEQyRzAoQE8LBaLvI8jK29vK6aWcVbzjhmmTmpwnJRhtjnP7nWQEY8pqQx2jdfRnDmRXT6BURWRf_A4yKbotVQWLC0rVJtI3_4y1czucDb8KyZTUE5VdD6jjnEZINhEh7KD5EQMEdA-Imzyt-trNc36aLBVumlf9NczbtP6Zxy3XHKkyEGtzXcat3UwuwRJdXVQKVFtcdNa1tBwHRYw7f1-ETWRTyh5I9SQ\"}"; + try { + JWK jwk = JWK.parse(str); + return jwk; + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + @Test + public void init() { + System.out.println("Inited!"); + } + + private static JWK getClientKey() { + String str = "{\"p\":\"5knPf4sOp5iQ5Z7NfWN3srqKUaasB69pIpn3X_bCiJ233Mkkye9kr5n7wxTZOU2Zmh_jdxF9OrBmIyWpoMptB3rC8A3c8NxF6dGAIkgB86krz26CeH7l_47so6cyYKuKaJUqXg2TzCWylzZ8fJ76Bx0yxA5B_1Sbz_Jnia6WeS8\",\"kty\":\"RSA\",\"q\":\"kcui1VGkfBD6m3DyY8hA68zfTl75HAiBuY_irct8qgtLg7dhxmRXqsSOkdZCHfSV0zalt8jS7dWl36_tFiXK2z-i13tP70EmDfJdh0OrA8QbAGRmXT8MUuKoUe8Lu5A5XqgHM6FwgfUWA5KYPAbLVi1sXLjXvPEKMXQf_K8qlJk\",\"d\":\"V4nLykoA63TciuCEP5JtjfCKvWprtZ8sELGnzR4Zfq2pjOjLHKzWZblJa7dacmgE2GRr7ZvIMBbrooJ8fuRnsM8BrTU4s6xz9z2887awUoWaO5sce9rCuio2SJtHpmw6vDI-oOQ42qy6qz_H7P6SyJz4T7qndhzFt3qZ9PcMPkpeacSSMLDGZs6GPkuSRL3CCw-kYaZFIkoL08Ivx8FrrV0YFv2L6egOnhrJJhW_gpQ3GkE4HyDsxIwp4JkPNOE02v-uL-_3pPrzenFLEG4Tf8FgGd6LvwBAE-5nH_fpxzMJmWqe0nmFcOtFjto3lPYjj9XBzkbtQb8DI-1qLxl6sQ\",\"e\":\"AQAB\",\"use\":\"enc\",\"kid\":\"8adbcbfe-199e-4a17-bfd1-821e86a1b47e\",\"qi\":\"LuWksKD__9o1LOyXkafJ6Tm5t7MT8SsDGb-GDT9gM7tOSJ6KZLKfDmtz2HEQNmMbjd-Z9N06pbIwkemE21iV6ps5rAaBGDbf83Npgvf-n3GKVR-k9Tgzmp48DQUAHUN7PfoAdKTm6JL0t8LpbGna394rMiNaZ6vgX8lJbClLaJU\",\"dp\":\"keG4HKfOhMTVJWDP89qK_SHGdasL12J7S3wVhSkgWsLusmKJd5K7SbJWFmKiqPZLk6MXyVm-5urQCPvW1RDmuJI_4yolCD_B2jjo3s6WzfAg3Kq44_QfZyD3L0S_WRXR_CZiTGp1ciF_XOMbQSbEZLVOb3xIuqKygayhgkqi7-k\",\"dq\":\"g448YEBLK6gmehxwm5kW-67h0NXh8mm6pLYw1KHI26dVfIT8tQfWE0FJZE7xWhZZGz00S6HqsrEV-8HDLTjs0umZBtc-SaV_sRYBTwzAQ6Wwt6ngtEMv25qqR0RsUdLR8Zes5-nEm3-LXa3psEBxOlHCdehUwyi9CeK--kMVKQ\",\"n\":\"gyb9iWIQPjWYkFNVIobtUMffutTzY25VMqyK1ZZbUCqnQ5FL-mej1Br8cRh7PNmKK798Q3DilFQtxhvu8Uxb9Z86ZunKLNj67JkBX0HAZFIf8wP_aKFIaq6yGp0vL5W6UwEEbNjmG8ja7NYNICNk0F28pLPxm5AmCrYxSTyMvtrdeg90nlq0aweQGF51Y717NvhR7gdbxpEjWbrkn0Y5J7Kv7MxRSz5pxg8xjsGBMtC6ZhxjxxseIfEauOfqPuThSKX2oijyMUzEBVK_Zw7nydG_AW8KAz0RpDmmq3-mQZw1kgrhZ7avR6Vqynv0JMt-slbNarap88aDwvcDWLOZFw\"}"; + try { + JWK jwk = JWK.parse(str); + return jwk; + } catch (ParseException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/doip/safetyTest/signatureTest/signatureClient.java b/src/main/java/doip/safetyTest/signatureTest/signatureClient.java new file mode 100644 index 0000000000000000000000000000000000000000..95e5761b2ee7af86bbe544252b6af586477d9388 --- /dev/null +++ b/src/main/java/doip/safetyTest/signatureTest/signatureClient.java @@ -0,0 +1,19 @@ +package doip.safetyTest.signatureTest; + +import java.io.IOException; +public class signatureClient { + public static void main(String[] args) throws IOException, InterruptedException { + signatureSuite client = new signatureSuite(); + client.config(); + client.testHelloReconnect(); + client.testCreateReconnect(); + client.testListOpsReconnect(); + client.testRetrieveReconnect(); + client.testSearchReconnect(); + client.testUpdateReconnect(); + client.testRetrieveReconnect(); + client.testDeleteReconnect(); + client.testRetrieveReconnect(); + client.checkTest(); + } +} diff --git a/src/main/java/doip/safetyTest/signatureTest/signatureSuite.java b/src/main/java/doip/safetyTest/signatureTest/signatureSuite.java new file mode 100644 index 0000000000000000000000000000000000000000..5338be811a405151f59677a229c45b34398968f7 --- /dev/null +++ b/src/main/java/doip/safetyTest/signatureTest/signatureSuite.java @@ -0,0 +1,536 @@ +package doip.safetyTest.signatureTest; + +import com.github.openjson.JSONObject; +import com.google.gson.Gson; +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.tools.picocli.CommandLine; +import org.bdware.doip.codec.digitalObject.DigitalObject; +import org.bdware.doip.codec.digitalObject.DoType; +import org.bdware.doip.codec.doipMessage.DoipMessage; +import org.bdware.doip.codec.doipMessage.DoipResponseCode; +import org.bdware.doip.codec.exception.DoDecodeException; +import org.bdware.doip.codec.metadata.SearchParameter; +import org.bdware.doip.encrypt.JWKCryptoManager; +import org.bdware.doip.encrypt.SM2Signer; +import org.bdware.doip.endpoint.client.ClientConfig; +import org.bdware.doip.endpoint.client.DoipClientImpl; +import org.bdware.doip.endpoint.client.DoipMessageCallback; +import doip.safetyTest.jwk.TestConstants; +import doip.safetyTest.jwk.TestClientKeyRetriever; +import org.bdware.doip.encrypt.*; +import org.junit.Test; +import org.springframework.aop.scope.ScopedProxyUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class signatureSuite { + static Logger LOGGER = LogManager.getLogger(signatureSuite.class); + int totalCount = 1; + static Scanner input = new Scanner(System.in); + static String serviceID = null; + final static DoipClientImpl doipClient = new DoipClientImpl(); + static DigitalObject saveDo; + static File file; + public static List outData = new ArrayList<>(10); +// static FileHandler fh; + + // public static Logger LOGGER = LogManager.getLogger(SecretTest.class); + public static PrintStream out = null; + public static String serviceAddr; + public static int answer = 0; + public static boolean flag = true; + public static int logo = 0; + + + public void config() throws IOException { + String[] flagData = {"Number", "Function", "Result", "Details"}; // 不通过 + outData.add(flagData); + file = new File("./config/clientConf.json"); + String content = FileUtils.readFileToString(file, "UTF-8"); + JSONObject jsonObject = new JSONObject(content); + serviceID = jsonObject.getString("serviceID"); + serviceAddr = jsonObject.getString("serviceAddr"); + + out = new PrintStream("./log/doipClient.log"); + } + + public void testHelloReconnect() throws InterruptedException { + LOGGER.info("----- Hello Test -----"); + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.hello(serviceID, msg -> { + assert isSignature(msg) == true; + isCertificate(msg); + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Hello not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Hello", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Hello Time Out -----"); + } + } else { + answer++; + String[] flagData = {logo + "", "0.DOIP/Op.Hello", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + correct.incrementAndGet(); + + DigitalObject retDo; + try { + retDo = msg.body.getDataAsDigitalObject(); + LOGGER.info(new Gson().toJson(retDo)); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert retDo.id.equals(serviceID); + LOGGER.info("----- Hello Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testCreateReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + DigitalObject dobj; + dobj = new DigitalObject("aibd", DoType.DO); + saveDo = dobj; + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.create(serviceID, dobj, msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Create", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Create not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Create", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Create Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoAlreadyExist) { + LOGGER.warn("----- Do Already Exists -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Create", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + try { + //byte[] str =msg.body.encodedData; + retDo = msg.body.getDataAsDigitalObject(); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert retDo != null; + LOGGER.info("----- Create Assert Pass -----"); + LOGGER.info(retDo); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testListOpsReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.listOperations("aibd", msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- ListOperations not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- ListOperations Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.ListOperations", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + String Jstr; + String newJstr; + String[] listArray; + try { + Jstr = new String(msg.body.encodedData, "UTF-8"); + newJstr = Jstr.substring(1, Jstr.length() - 1); + LOGGER.info(newJstr); + listArray = newJstr.split(","); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + assert Arrays.asList(listArray).contains("0.DOIP/Op.Create"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Retrieve"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Update"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.Delete"); + assert Arrays.asList(listArray).contains("0.DOIP/Op.ListOperations"); + LOGGER.info("----- ListOps Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testUpdateReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + DigitalObject dobj = new DigitalObject("aibd", DoType.DOIPServiceInfo); + dobj.addAttribute("syw", "aibd"); + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.update(dobj, msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- Update not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.warn("----- Update Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("- [FAIL] Proxy & Server Create ------ Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Update", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + String jsonRetDo; + try { + //byte[] str =msg.body.encodedData; + retDo = msg.body.getDataAsDigitalObject(); + jsonRetDo = new Gson().toJson(retDo); + LOGGER.info(new Gson().toJson(retDo)); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + assert saveDo.equals(retDo); + LOGGER.info("----- Update Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testRetrieveReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.retrieve("aibd", null, false, msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + LOGGER.warn("----- Retrieve not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Retrieve Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Retrieve", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + + DigitalObject retDo; + try { + retDo = msg.body.getDataAsDigitalObject(); + assert retDo.id.toString() == "aibd"; + assert saveDo.equals(retDo); + LOGGER.info("----- Retrieve Assert Pass -----"); + LOGGER.info(retDo.toString()); + } catch (DoDecodeException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testSearchReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + + String query = "aibd"; + SearchParameter sp = new SearchParameter(query, 10, 0, "", "DO"); + sp.query = query; + + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.search(query, sp, msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + System.out.println("response : "+msg.header.parameters.response); + LOGGER.warn("----- Search not Success -----"); + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Search Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Search", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + DigitalObject retDo; + try { + String s_new = new String(msg.body.encodedData, "UTF-8"); + LOGGER.info("Search Result : " + s_new); + LOGGER.info("----- Search Assert Pass -----"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public void testDeleteReconnect() throws InterruptedException { + long start = System.currentTimeMillis(); + final AtomicInteger total = new AtomicInteger(0); + final AtomicInteger correct = new AtomicInteger(0); + for (int i = 0; i < totalCount; i++) { + final DoipClientImpl doipClient = new DoipClientImpl(); + doipClient.connect(ClientConfig.fromUrl(serviceAddr) + .setDebugPrint(true) + .setSigner(new SM2Signer(TestConstants.clientSM2))); + doipClient.delete("aibd", msg -> { + assert isSignature(msg) == true; + logo++; + if (flag == false) { + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "NULL", "null"}; + outData.add(flagData); + } else { + if (msg.header.parameters.response != DoipResponseCode.Success) { + LOGGER.warn("----- Delete not Success -----"); + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "Not Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + flag = false; + if (msg.header.parameters.response == DoipResponseCode.UnKnownError) { + LOGGER.error("----- Delete Time Out -----"); + } else if (msg.header.parameters.response == DoipResponseCode.DoNotFound) { + LOGGER.error("----- Do Not Found -----"); + } + } else { + String[] flagData = {logo + "", "0.DOIP/Op.Delete", "Pass", msg.header.parameters.response.toString()}; // 不通过 + outData.add(flagData); + answer++; + correct.incrementAndGet(); + //LOGGER.info("Retrieved:" + str + //+ " respCode:" + msg.header.parameters.response); + + if (msg.header.parameters.response.toString() == "Success") { + assert msg.header.parameters.response.toString() == "Success"; + } else { + assert msg.header.parameters.response == DoipResponseCode.DoNotFound; + } + LOGGER.info("----- Delete Assert Pass -----"); + } + } + total.incrementAndGet(); + if (doipClient != null) doipClient.close(); + }); + } + int circle = 0; + for (; total.get() < totalCount; ) { + if (++circle % 100 == 0) + LOGGER.info(String.format("%d/%d", correct.get(), total.get())); + Thread.sleep(10); + } + int dur = (int) (System.currentTimeMillis() - start); + LOGGER.info(String.format("Final Result:%d/%d dur:%d", correct.get(), total.get(), dur)); + } + + public String escapeSpecialCharacters(String data) { + String escapedData = data.replaceAll("\\R", " "); + if (data.contains(",") || data.contains("\"") || data.contains("'")) { + data = data.replace("\"", "\"\""); + escapedData = "\"" + data + "\""; + } + return escapedData; + } + + public String convertToCSV(String[] data) { + return Stream.of(data) + .map(this::escapeSpecialCharacters) + .collect(Collectors.joining(",")); + } + public void wirterCsv(String csvFileName, List data) + throws FileNotFoundException, UnsupportedEncodingException { + File csvOutputFile = new File(csvFileName); + try (PrintWriter pw = new PrintWriter(csvOutputFile, "GBK")) { + data.stream() + .map(this::convertToCSV) + .forEach(pw::println); + } + } + + public void checkTest() throws FileNotFoundException, UnsupportedEncodingException { + wirterCsv("./log/signatureTest.csv",outData); + if(answer == 8){ + LOGGER.info("----- All Signature Tests Pass! -----"); + }else{ + LOGGER.info("----- Not Pass , Please Check! -----"); + } + } + boolean isCertificate(DoipMessage response){ + return ((response.header.getFlag() >> 30) == 1); + } + + boolean isSignature(DoipMessage response){ + return response.credential != null; + } +} diff --git a/src/main/java/doip/sampleRepository/SampleRepoHandler.java b/src/main/java/doip/sampleRepository/SampleRepoHandler.java index 2f787679dec3e6136a7c48e2a85d669e13ea606e..f576f77d72f84ed9d3077ce5533a8d8acd153830 100644 --- a/src/main/java/doip/sampleRepository/SampleRepoHandler.java +++ b/src/main/java/doip/sampleRepository/SampleRepoHandler.java @@ -4,6 +4,7 @@ import io.netty.channel.ChannelHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bdware.doip.codec.digitalObject.DigitalObject; +import org.bdware.doip.codec.digitalObject.DoType; import org.bdware.doip.codec.digitalObject.Element; import org.bdware.doip.codec.doipMessage.DoipMessage; import org.bdware.doip.codec.doipMessage.DoipResponseCode; @@ -16,6 +17,7 @@ import org.bdware.doip.endpoint.server.Op; import org.bdware.doip.endpoint.server.RegistryHandlerBase; import java.io.IOException; +import java.sql.SQLOutput; import java.util.HashMap; @ChannelHandler.Sharable @@ -31,8 +33,9 @@ public class SampleRepoHandler extends RegistryHandlerBase { @Override public DoipMessage handleHello(DoipMessage request) { LOGGER.info(" handleHello "); - if(request.header.parameters.id.equals(serviceInfo.id)) + if(request.header.parameters.id.equals(serviceInfo.id)){ return replyDoipServiceInfo(request); + } return replyStringWithStatus(request,"serviceID do not match", DoipResponseCode.Declined); } @@ -44,9 +47,9 @@ public class SampleRepoHandler extends RegistryHandlerBase { @Override public synchronized DoipMessage handleCreate(DoipMessage request) { - LOGGER.info(" handleCreate "); + LOGGER.info("----- handleCreate -----"); if(request.header.parameters.id.equals(serviceInfo.id)){ - DigitalObject digitalObject = null; + DigitalObject digitalObject; try { digitalObject = request.body.getDataAsDigitalObject(); } catch (DoDecodeException e) { @@ -82,7 +85,7 @@ public class SampleRepoHandler extends RegistryHandlerBase { @Override public DoipMessage handleUpdate(DoipMessage request) { - LOGGER.info(" handleUpdate "); + LOGGER.info("----- handleUpdate -----"); String targetId = request.header.parameters.id; if(!digitalObjectHashMap.containsKey(targetId)) { return replyStringWithStatus(request,"do not found",DoipResponseCode.DoNotFound); @@ -103,18 +106,20 @@ public class SampleRepoHandler extends RegistryHandlerBase { @Override public DoipMessage handleDelete(DoipMessage request) { - LOGGER.info(" handleDelete "); + LOGGER.info("----- handleDelete -----"); String targetId = request.header.parameters.id; if(!digitalObjectHashMap.containsKey(targetId)) { + System.out.println("----- Server Can Not Found Do -----"); return replyStringWithStatus(request,"do not found",DoipResponseCode.DoNotFound); } digitalObjectHashMap.remove(targetId); + System.out.println("----- Server Delete Success -----"); return replyString(request,"success"); } @Override public DoipMessage handleRetrieve(DoipMessage request) { - LOGGER.info(" handleRetrieve "); + LOGGER.info("----- handleRetrieve -----"); String targetId = request.header.parameters.id; if(!digitalObjectHashMap.containsKey(targetId)) { return replyStringWithStatus(request,"do not found",DoipResponseCode.DoNotFound); @@ -148,8 +153,10 @@ public class SampleRepoHandler extends RegistryHandlerBase { @Override public DoipMessage handleSearch(DoipMessage request) { - LOGGER.info(" handleSearch "); - if(request.header.parameters == null || request.header.parameters.attributes == null || request.header.parameters.attributes.get("query") == null){ + LOGGER.info("----- handleSearch -----"); + + if(request.header.parameters == null || request.header.parameters.attributes == null || request.header.parameters.attributes.has("query") == false){ + System.out.println("----- Server Search Failed -----"); LOGGER.info("invalid search request: attributes not found"); return replyStringWithStatus(request, "invalid search request: attributes not found.", DoipResponseCode.Invalid); } @@ -160,11 +167,15 @@ public class SampleRepoHandler extends RegistryHandlerBase { // !request.header.parameters.attributes.has("type")?null:request.header.parameters.attributes.get("type").getAsString()); //only support query by id - DigitalObject digitalObject = digitalObjectHashMap.get(request.header.parameters.attributes.get("query").getAsString()); - DigitalObject retDO = new DigitalObject(digitalObject.id,digitalObject.type); - retDO.attributes = digitalObject.attributes; + DigitalObject digitalObject = digitalObjectHashMap.get(request.header.parameters.attributes.get("query").toString()); +// System.out.println("Server DOID : " +digitalObject.id); +// System.out.println("Server Dotype : "+digitalObject.type); +// DigitalObject retDO = new DigitalObject(digitalObject.id,digitalObject.type); +// retDO.attributes = digitalObject.attributes; + DigitalObject retDO = new DigitalObject("syw", DoType.DO); SearchResult sr = new SearchResult(); sr.addItem(retDO); + System.out.println("----- Server Search Success -----"); return replyString(request, DoipGson.getDoipGson().toJson(sr)); } @@ -172,6 +183,4 @@ public class SampleRepoHandler extends RegistryHandlerBase { public DoipMessage handleOwnExtension(DoipMessage request) { return null; } - - } diff --git a/src/main/java/doip/sampleRepository/SampleRepositoryMain.java b/src/main/java/doip/sampleRepository/SampleRepositoryMain.java index 3c6610e6958877b246d809e2739c8e00acc55b59..7604118183db94b9bf4dbeed82a652d9f0d92852 100644 --- a/src/main/java/doip/sampleRepository/SampleRepositoryMain.java +++ b/src/main/java/doip/sampleRepository/SampleRepositoryMain.java @@ -24,6 +24,5 @@ public class SampleRepositoryMain { server.setRepositoryHandler(new SampleRepoHandler(serviceInfo)); server.start(); - } }