diff --git a/core/src/main/java/org/bouncycastle/asn1/gm/SM2Cipher.java b/core/src/main/java/org/bouncycastle/asn1/gm/SM2Cipher.java new file mode 100644 index 0000000000..5a9b149a74 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/asn1/gm/SM2Cipher.java @@ -0,0 +1,180 @@ +package org.bouncycastle.asn1.gm; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.util.BigIntegers; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Enumeration; + +/** + * GMT 0009-2012 + *

+ * sm2 encrypted data specific struct + * + * + * @since 2021-03-10 13:28:12 + */ +public class SM2Cipher extends ASN1Object +{ + /* + * SM2Cipher ::== SEQUENCE{ + * XCoordinate INTEGER, --X Portion + * YCoordinate INTEGER, --Y Portion + * HASH OCTET STRING SIZE(32), --Plaintext sm3 hash + * CipherText OCTET STRING --CipherText + * } + */ + + private ASN1Integer xCoordinate; + private ASN1Integer yCoordinate; + private ASN1OctetString hash; + private ASN1OctetString cipherText; + + public SM2Cipher() + { + super(); + } + + public SM2Cipher(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + xCoordinate = ASN1Integer.getInstance(e.nextElement()); + yCoordinate = ASN1Integer.getInstance(e.nextElement()); + hash = ASN1OctetString.getInstance(e.nextElement()); + cipherText = ASN1OctetString.getInstance(e.nextElement()); + } + + public static SM2Cipher getInstance(Object o) + { + if(o instanceof SM2Cipher) + { + return (SM2Cipher) o; + } + else if(o != null) + { + return new SM2Cipher(ASN1Sequence.getInstance(o)); + } + return null; + } + + public ASN1Integer getxCoordinate() + { + return xCoordinate; + } + + public void setxCoordinate(ASN1Integer xCoordinate) + { + this.xCoordinate = xCoordinate; + } + + public ASN1Integer getyCoordinate() + { + return yCoordinate; + } + + public void setyCoordinate(ASN1Integer yCoordinate) + { + this.yCoordinate = yCoordinate; + } + + public ASN1OctetString getHash() + { + return hash; + } + + public void setHash(ASN1OctetString hash) + { + this.hash = hash; + } + + public ASN1OctetString getCipherText() + { + return cipherText; + } + + public void setCipherText(ASN1OctetString cipherText) + { + this.cipherText = cipherText; + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(4); + v.add(xCoordinate); + v.add(yCoordinate); + v.add(hash); + v.add(cipherText); + return new DERSequence(v); + } + + /** + * Convert ASN.1 Struct to C1C3C2 format + * + * @return C1C3C2 + * @throws IOException + */ + public byte[] convertC1C3C2() throws IOException + { + /* + * construct GMT0009-2012 encrypted data struct + */ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + + final byte[] x = new byte[32]; + final byte[] y = new byte[32]; + + byte[] tmp = BigIntegers.asUnsignedByteArray(getxCoordinate().getValue()); + System.arraycopy(tmp, 0, x, 32 - tmp.length, tmp.length); + tmp = BigIntegers.asUnsignedByteArray(getyCoordinate().getValue()); + System.arraycopy(tmp, 0, y, 32 - tmp.length, tmp.length); + + // C1 + // read 1 byte for uncompressed point prefix 0x04 + stream.write(0x04); + stream.write(x); + stream.write(y); + // C3 + stream.write(getHash().getOctets()); + // C2 + stream.write(getCipherText().getOctets()); + stream.flush(); + return stream.toByteArray(); + } + + /** + * Convert SM2 encrypted result format of c1c3c2 to ASN.1 SM2Cipher + * + * @param c1c3c2 encrypted result + * @return SM2Cipher + * @throws IOException + */ + static public SM2Cipher fromC1C3C2(byte[] c1c3c2) throws IOException + { + /* + * construct GMT0009-2012 encrypted data struct + */ + ByteArrayInputStream stream = new ByteArrayInputStream(c1c3c2); + // read 1 byte for uncompressed point prefix 0x04 + stream.read(); + final byte[] x = new byte[32]; + final byte[] y = new byte[32]; + final byte[] hash = new byte[32]; + int length = c1c3c2.length - 1 - 32 - 32 - 32; + final byte[] cipherText = new byte[length]; + stream.read(x); + stream.read(y); + stream.read(hash); + stream.read(cipherText); + + final SM2Cipher sm2Cipher = new SM2Cipher(); + sm2Cipher.setxCoordinate(new ASN1Integer(new BigInteger(1, x))); + sm2Cipher.setyCoordinate(new ASN1Integer(new BigInteger(1, y))); + sm2Cipher.setHash(new DEROctetString(hash)); + sm2Cipher.setCipherText(new DEROctetString(cipherText)); + return sm2Cipher; + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/CipherSuiteInfo.java b/tls/src/main/java/org/bouncycastle/jsse/provider/CipherSuiteInfo.java index 0e67d1db1e..06c50a3273 100644 --- a/tls/src/main/java/org/bouncycastle/jsse/provider/CipherSuiteInfo.java +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/CipherSuiteInfo.java @@ -16,7 +16,7 @@ class CipherSuiteInfo { static CipherSuiteInfo forCipherSuite(int cipherSuite, String name) { - if (!name.startsWith("TLS_")) + if (!name.startsWith("TLS_") && !name.startsWith("GMSSL_")) { throw new IllegalArgumentException(); } @@ -81,6 +81,14 @@ boolean isTLSv13() return isTLSv13; } + /** + * GMSSL 1.1 crypto suites Start with 0xe0 + * @return true - GMSSL suite; false - not + */ + boolean isGMSSLv11(){ + return ((cipherSuite >> 8) & 0xFF) == 0xe0; + } + private static void addAll(Set decomposition, String... entries) { for (String entry : entries) @@ -150,6 +158,9 @@ private static void decomposeEncryptionAlgorithm(Set decomposition, int case EncryptionAlgorithm.CHACHA20_POLY1305: // NOTE: Following SunJSSE, nothing beyond the transformation added above (i.e "ChaCha20-Poly1305") break; + case EncryptionAlgorithm.SM4_CBC: + decomposition.add("SM4_CBC"); + break; case EncryptionAlgorithm.NULL: decomposition.add("C_NULL"); break; @@ -170,6 +181,9 @@ private static void decomposeHashAlgorithm(Set decomposition, short hash case HashAlgorithm.sha384: addAll(decomposition, "SHA384", "SHA-384", "HmacSHA384"); break; + case HashAlgorithm.sm3: + addAll(decomposition, "SM3"); + break; // case HashAlgorithm.sha512: // addAll(decomposition, "SHA512", "SHA-512", "HmacSHA512"); // break; @@ -200,6 +214,9 @@ private static void decomposeKeyExchangeAlgorithm(Set decomposition, int case KeyExchangeAlgorithm.RSA: addAll(decomposition, "RSA"); break; + case KeyExchangeAlgorithm.SM2: + addAll(decomposition, "SM2"); + break; default: throw new IllegalArgumentException(); } @@ -227,6 +244,9 @@ private static void decomposeMACAlgorithm(Set decomposition, int cipherT case MACAlgorithm.hmac_sha384: addAll(decomposition, "SHA384", "SHA-384", "HmacSHA384"); break; + case MACAlgorithm.hmac_sm3: + addAll(decomposition, "SM3", "HmacSM3"); + break; // case MACAlgorithm.hmac_sha512: // addAll(decomposition, "SHA512", "SHA-512", "HmacSHA512"); // break; @@ -354,6 +374,9 @@ private static short getHashAlgorithm(int cipherSuite) case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384: return HashAlgorithm.sha384; + case CipherSuite.GMSSL_ECC_SM4_SM3: + return HashAlgorithm.sm3; + default: throw new IllegalArgumentException(); } @@ -392,6 +415,8 @@ private static String getTransformation(int encryptionAlgorithm) return "ChaCha20-Poly1305"; case EncryptionAlgorithm.NULL: return "NULL"; + case EncryptionAlgorithm.SM4_CBC: + return "SM4/CBC/NoPadding"; default: throw new IllegalArgumentException(); } diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSession.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSession.java new file mode 100644 index 0000000000..86cd5923be --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSession.java @@ -0,0 +1,298 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.tls.crypto.TlsCertificate; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.CertificateException; +import javax.security.cert.X509Certificate; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.SocketException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Empty session to prevent errors + * + * @since 2021-03-17 16:35:18 + */ +public class GMSession implements SSLSession +{ + private boolean valid = true; + + private long created; + private long updated; + + private Map contextValue; + + private GMSimpleSSLSocket gmScoket; + + private SecurityParameterProvider secParamProvider; + + private CertificateFactory cf; + + public GMSession(GMSimpleSSLSocket scoket) + { + gmScoket = scoket; + contextValue = new HashMap(); + renew(null); + + try + { + cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider()); + } + catch (java.security.cert.CertificateException e) + { + throw new RuntimeException(e); + } + } + + public void renew(SecurityParameterProvider secParamProvider) + { + final long now = System.currentTimeMillis(); + if (created == 0) { + created = now; + } + updated = now; + this.secParamProvider = secParamProvider; + } + + public byte[] getId() + { + if (secParamProvider == null) + { + return new byte[0]; + } + return secParamProvider.getSecurityParameters().getSessionID(); + } + + public SSLSessionContext getSessionContext() + { + return null; + } + + public long getCreationTime() + { + return this.created; + } + + public long getLastAccessedTime() + { + return this.updated; + } + + public void invalidate() + { + valid = false; + } + + public boolean isValid() + { + return valid; + } + + public void putValue(String s, Object o) + { + this.contextValue.put(s, o); + } + + public Object getValue(String s) + { + return this.contextValue.get(s); + } + + public void removeValue(String s) + { + this.contextValue.remove(s); + } + + public String[] getValueNames() + { + final Set keySet = this.contextValue.keySet(); + final int len = keySet.size(); + String[] res = new String[len]; + int i = 0; + for (String s : keySet) + { + res[i] = s; + i++; + } + return res; + } + + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException + { + if (secParamProvider == null) + { + return new Certificate[0]; + } + final org.bouncycastle.tls.Certificate peerCertificate = + secParamProvider.getSecurityParameters().getPeerCertificate(); + final TlsCertificate[] list = peerCertificate.getCertificateList(); + Certificate[] res = new Certificate[list.length]; + for (int i = 0; i < list.length; i++) + { + try + { + final byte[] encoded = list[i].getEncoded(); + res[i] = cf.generateCertificate(new ByteArrayInputStream(encoded)); + } + catch (java.security.cert.CertificateException e) + { + throw new SSLPeerUnverifiedException("peer cert encoded error: " + e.getMessage()); + } + catch (IOException e) + { + throw new SSLPeerUnverifiedException("peer cert encoded error: " + e.getMessage()); + } + } + return res; + } + + public Certificate[] getLocalCertificates() + { + if (secParamProvider == null) + { + return new Certificate[0]; + } + + final org.bouncycastle.tls.Certificate localCertificate = + secParamProvider.getSecurityParameters().getLocalCertificate(); + final TlsCertificate[] list = localCertificate.getCertificateList(); + Certificate[] res = new Certificate[list.length]; + for (int i = 0; i < list.length; i++) + { + try + { + + final byte[] encoded = list[i].getEncoded(); + res[i] = cf.generateCertificate(new ByteArrayInputStream(encoded)); + } + catch (java.security.cert.CertificateException e) + { + throw new RuntimeException("local cert encoded error: " + e.getMessage()); + } + catch (IOException e) + { + throw new RuntimeException("local cert encoded error: " + e.getMessage()); + } + } + return res; + } + + public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException + { + if (secParamProvider == null) + { + return new X509Certificate[0]; + } + + org.bouncycastle.tls.Certificate peerCert = + secParamProvider.getSecurityParameters().getPeerCertificate(); + + final TlsCertificate[] list = peerCert.getCertificateList(); + X509Certificate[] res = new X509Certificate[list.length]; + for (int i = 0; i < list.length; i++) { + try + { + final byte[] encoded = list[i].getEncoded(); + res[i] = X509Certificate.getInstance(encoded); + } + catch (IOException e) + { + throw new SSLPeerUnverifiedException("peer cert encoded error: " + e.getMessage()); + } + catch (CertificateException e) + { + throw new SSLPeerUnverifiedException("peer cert encoded error: " + e.getMessage()); + } + + } + return res; + } + + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException + { + final X509Certificate[] peerCertificateChain = this.getPeerCertificateChain(); + if (peerCertificateChain == null || peerCertificateChain.length == 0) + { + return null; + } + return peerCertificateChain[0].getSubjectDN(); + } + + public Principal getLocalPrincipal() + { + org.bouncycastle.tls.Certificate cert = + secParamProvider.getSecurityParameters().getLocalCertificate(); + + final TlsCertificate[] list = cert.getCertificateList(); + if (list.length == 0){ + return null; + } + final byte[] encoded; + try { + encoded = list[0].getEncoded(); + return X509Certificate.getInstance(encoded).getSubjectDN(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + catch (CertificateException e) + { + throw new RuntimeException(e); + } + } + + public String getCipherSuite() + { + return "GMSSL_SM2_SM4_SM3"; + } + + public String getProtocol() + { + return "GMSSLv1.1"; + } + + public String getPeerHost() + { + return gmScoket.getRemoteSocketAddress().toString(); + } + + public int getPeerPort() + { + return gmScoket.getPort(); + } + + public int getPacketBufferSize() + { + try + { + return gmScoket.getSendBufferSize(); + } + catch (SocketException e) + { + return 0; + } + } + + public int getApplicationBufferSize() + { + try + { + return gmScoket.getSendBufferSize(); + } + catch (SocketException e) + { + return 0; + } + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLClient.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLClient.java new file mode 100644 index 0000000000..21c552d3cc --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLClient.java @@ -0,0 +1,103 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.tls.*; +import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Hashtable; + +/** + * Simple GMSSL client + *

+ * - make handshake connection + * - no authentication + * + * @since 2021-03-09 14:01:50 + */ +public class GMSimpleSSLClient extends AbstractTlsClient +{ + + + private static final int[] DEFAULT_CIPHER_SUITES = new int[] + { + /* + * GMSSL 1.1 + */ + CipherSuite.GMSSL_ECC_SM4_SM3, + }; + + public GMSimpleSSLClient() + { + this(new BcTlsCrypto(new SecureRandom())); + } + + public GMSimpleSSLClient(TlsCrypto crypto) + { + super(crypto); + } + + @Override + protected ProtocolVersion[] getSupportedVersions() + { + return new ProtocolVersion[]{ProtocolVersion.GMSSLv11}; + } + + protected int[] getSupportedCipherSuites() + { + return TlsUtils.getSupportedCipherSuites(getCrypto(), DEFAULT_CIPHER_SUITES); + } + + public TlsAuthentication getAuthentication() throws IOException + { + return new TlsAuthentication() + { + + public void notifyServerCertificate(TlsServerCertificate serverCertificate) throws IOException + { +// System.out.println(">> TlsAuthentication on notifyServerCertificate"); +// System.out.println(serverCertificate.getCertificate()); + } + + public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException + { +// System.out.println(">> TlsAuthentication on getClientCredentials"); + return null; + } + }; + } + + /** + * GMSSL not support ClientExtensions + * + * @return empty list + * @throws IOException not happen + */ + @Override + public Hashtable getClientExtensions() throws IOException + { + return new Hashtable(0); + } + + /** + * GMSSL Client generate random struct should be + * struct + * { + * unit32 gmt_unix_time; + * opaque random_bytes[28]; + * } + * + * @return true - use GMTUnixTime + */ + @Override + public boolean shouldUseGMTUnixTime() + { + return true; + } + + @Override + public void notifySecureRenegotiation(boolean secureRenegotiation) throws IOException + { + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLServer.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLServer.java new file mode 100644 index 0000000000..a6f4782fff --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLServer.java @@ -0,0 +1,121 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.tls.*; +import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.impl.bc.BcGMSSLCredentials; +import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto; +import org.bouncycastle.util.encoders.Hex; + +import java.io.IOException; +import java.io.PrintStream; +import java.security.SecureRandom; + +/** + * Simple GMSSL Server + * + * + * @since 2021-03-16 09:31:14 + */ +public class GMSimpleSSLServer + extends DefaultTlsServer +{ + /* + * contain two cert, first for sign, second for encrypt + */ + protected Certificate certList; + protected AsymmetricKeyParameter signKey; + protected AsymmetricKeyParameter encKey; + + /** + * Create GMSSL Server Instance + * + * @param crypto crypto + * @param certList contain two cert, first for sign, second for encrypt + * @param signKey sign private key + * @param encKey encrypt private key + */ + public GMSimpleSSLServer(TlsCrypto crypto, Certificate certList, AsymmetricKeyParameter signKey, AsymmetricKeyParameter encKey) + { + super(crypto); + this.certList = certList; + this.signKey = signKey; + this.encKey = encKey; + } + + public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) + { +// PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out; +// out.println("GMSSL server raised alert: " + AlertLevel.getText(alertLevel) +// + ", " + AlertDescription.getText(alertDescription)); +// if (message != null) +// { +// out.println("> " + message); +// } +// if (cause != null) +// { +// cause.printStackTrace(out); +// } + } + + public void notifyAlertReceived(short alertLevel, short alertDescription) + { +// PrintStream out = (alertLevel == AlertLevel.fatal) ? System.err : System.out; +// out.println("GMSSL server received alert: " + AlertLevel.getText(alertLevel) +// + ", " + AlertDescription.getText(alertDescription)); + } + + @Override + public void notifySecureRenegotiation(boolean secureRenegotiation) throws IOException + { + + } + + public ProtocolVersion getServerVersion() throws IOException + { + return ProtocolVersion.GMSSLv11; + } + + public CertificateRequest getCertificateRequest() throws IOException + { + return null; + } + + public void notifyClientCertificate(Certificate clientCertificate) throws IOException + { + + } + + public void notifyHandshakeComplete() throws IOException + { + + } + + @Override + public TlsCredentials getCredentials() throws IOException + { + return new BcGMSSLCredentials((BcTlsCrypto) getCrypto(), certList, signKey, encKey); + } + + @Override + public boolean shouldUseGMTUnixTime() + { + return true; + } + + protected int[] getSupportedCipherSuites() + { + return new int[]{CipherSuite.GMSSL_ECC_SM4_SM3}; + } + + protected ProtocolVersion[] getSupportedVersions() + { + return ProtocolVersion.GMSSLv11.only(); + } + + protected String hex(byte[] data) + { + return data == null ? "(null)" : Hex.toHexString(data); + } + +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocket.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocket.java new file mode 100644 index 0000000000..cd2bac2d63 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocket.java @@ -0,0 +1,295 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.tls.*; +import org.bouncycastle.tls.crypto.TlsCrypto; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.SocketAddress; + +/** + * GMSSL Socket simple implement. + * + * + * @since 2021-03-16 13:18:06 + */ +public class GMSimpleSSLSocket extends SSLSocket +{ + public static final String[] SUPPORT_CRYPTO_SUITES = { + "GMSSL_SM2_SM4_SM3" + }; + + public static final String[] SUPPORT_PROTOCOL_VERSIONS = { + "GMSSLv1.1" + }; + + protected boolean useClientMode = true; + private TlsProtocol protocol; + + protected TlsCrypto crypto; + protected Certificate certList; + protected AsymmetricKeyParameter signKey, encKey; + + private GMSession session; + + + protected GMSimpleSSLSocket() + { + super(); + } + + public GMSimpleSSLSocket(String host, int port) throws IOException + { + super(host, port); + } + + public GMSimpleSSLSocket(InetAddress host, int port) throws IOException + { + super(host, port); + } + + public GMSimpleSSLSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException + { + super(host, port, localAddress, localPort); + } + + public GMSimpleSSLSocket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException + { + super(host, port, localAddress, localPort); + } + + /** + * set socket use client mode + * + * @param crypto crypto + * @return GMSimpleSSLSocket + */ + public GMSimpleSSLSocket useClientMode(TlsCrypto crypto) + { + this.useClientMode = true; + this.crypto = crypto; + this.certList = null; + this.signKey = null; + this.encKey = null; + return this; + } + + /** + * set socket use server mode + * + * @param crypto crypto + * @param certList sign cert and encrypt cert + * @param signKey sign keypair + * @param encKey enc keypair + * @return GMSimpleSSLSocket + */ + public GMSimpleSSLSocket useServerMode(TlsCrypto crypto, Certificate certList, AsymmetricKeyParameter signKey, AsymmetricKeyParameter encKey) + { + this.useClientMode = false; + this.crypto = crypto; + this.certList = certList; + this.signKey = signKey; + this.encKey = encKey; + return this; + } + + + @Override + public String[] getSupportedCipherSuites() + { + return SUPPORT_CRYPTO_SUITES; + } + + @Override + public String[] getEnabledCipherSuites() + { + return SUPPORT_CRYPTO_SUITES; + } + + @Override + public void setEnabledCipherSuites(String[] strings) + { + + } + + @Override + public String[] getSupportedProtocols() + { + return SUPPORT_PROTOCOL_VERSIONS; + } + + @Override + public String[] getEnabledProtocols() + { + return SUPPORT_PROTOCOL_VERSIONS; + } + + @Override + public void setEnabledProtocols(String[] strings) + { + + } + + @Override + public void connect(SocketAddress endpoint) throws IOException + { + super.connect(endpoint); + } + + @Override + public SSLSession getSession() + { + // prevent apache HttpClient get session null throw error + return session; + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener handshakeCompletedListener) + { + + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener handshakeCompletedListener) + { + + } + + @Override + public void startHandshake() throws IOException + { + InputStream input = super.getInputStream(); + OutputStream output = super.getOutputStream(); + + makeHandshake(input, output); + } + + protected void makeHandshake(InputStream input, OutputStream output) throws IOException + { + if(session == null) + { + session = new GMSession(this); + } + if(this.useClientMode) + { + GmSimpleTlsClientProtocol clientProtocol = new GmSimpleTlsClientProtocol(input, output); + this.protocol = clientProtocol; + session.renew(clientProtocol); + GMSimpleSSLClient client = new GMSimpleSSLClient(crypto); + + clientProtocol.connect(client); + } + else + { + GMSimpleSSLServer server = new GMSimpleSSLServer(crypto, certList, signKey, encKey); + GmSimpleTlsServerProtocol serverProtocol = new GmSimpleTlsServerProtocol(input, output); + session.renew(serverProtocol); + this.protocol = serverProtocol; + serverProtocol.accept(server); + } + + } + + synchronized void handshakeIfNecessary() throws IOException + { + + if (protocol == null || protocol.isHandshaking()) + { + startHandshake(); + } + } + + + @Override + public InputStream getInputStream() throws IOException + { + handshakeIfNecessary(); + return this.protocol.getInputStream(); + } + + @Override + public OutputStream getOutputStream() throws IOException + { + handshakeIfNecessary(); + return this.protocol.getOutputStream(); + } + + @Override + public void setUseClientMode(boolean b) + { + this.useClientMode = b; + } + + @Override + public boolean getUseClientMode() + { + return useClientMode; + } + + @Override + public void setNeedClientAuth(boolean b) + { + } + + @Override + public boolean getNeedClientAuth() + { + return false; + } + + @Override + public void setWantClientAuth(boolean b) + { + + } + + @Override + public boolean getWantClientAuth() + { + return false; + } + + @Override + public void setEnableSessionCreation(boolean b) + { + + } + + @Override + public boolean getEnableSessionCreation() + { + return false; + } + + @Override + public synchronized void close() throws IOException + { + if(protocol != null) + { + protocol.close(); + } + } + + + + @Override + protected void finalize() throws Throwable + { + try + { + close(); + } catch (IOException e) + { + // Ignore + } finally + { + super.finalize(); + } + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketFactory.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketFactory.java new file mode 100644 index 0000000000..ffd910d3a0 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketFactory.java @@ -0,0 +1,135 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.jcajce.provider.asymmetric.GM; +import org.bouncycastle.tls.Certificate; +import org.bouncycastle.tls.crypto.TlsCrypto; +import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto; + +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.SecureRandom; + +/** + * Simple GMSSL Socket Factory + * + * + * @since 2021-03-16 13:15:00 + */ +public class GMSimpleSSLSocketFactory extends SSLSocketFactory +{ + + private TlsCrypto crypto; + private Certificate certList; + private AsymmetricKeyParameter signKey, encKey; + private Boolean clientMode = true; + + + /** + * Create Client side socket factory Instance + */ + public GMSimpleSSLSocketFactory() + { + this.crypto = new BcTlsCrypto(new SecureRandom()); + } + + /** + * Create server side socket factory Instance + * + * @param certList sign cert and encrypt cert + * @param signKey sign key + * @param encKey enc key + */ + public GMSimpleSSLSocketFactory(Certificate certList, AsymmetricKeyParameter signKey, AsymmetricKeyParameter encKey) + { + this(); + this.certList = certList; + this.signKey = signKey; + this.encKey = encKey; + clientMode = false; + } + + /** + * Create Client side socket factory Instance + * @return factory Instance + */ + public static GMSimpleSSLSocketFactory ClientFactory() + { + return new GMSimpleSSLSocketFactory(); + } + + /** + * Create server side socket factory Instance + * + * @param certList sign cert and encrypt cert + * @param signKey sign key + * @param encKey enc key + */ + public static GMSimpleSSLSocketFactory ServerFactory(Certificate certList, AsymmetricKeyParameter signKey, + AsymmetricKeyParameter encKey) + { + return new GMSimpleSSLSocketFactory(certList, signKey,encKey); + } + + @Override + public String[] getDefaultCipherSuites() + { + return GMSimpleSSLSocket.SUPPORT_CRYPTO_SUITES; + } + + @Override + public String[] getSupportedCipherSuites() + { + return GMSimpleSSLSocket.SUPPORT_CRYPTO_SUITES; + } + + private GMSimpleSSLSocket ret(GMSimpleSSLSocket socket) + { + if(clientMode) + { + return socket.useClientMode(crypto); + } + else + { + return socket.useServerMode(crypto, certList, signKey, encKey); + } + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException + { + final GMSimpleSSLSocketWrap wrap = new GMSimpleSSLSocketWrap(socket, autoClose); + return ret(wrap); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException + { + final GMSimpleSSLSocket socket = new GMSimpleSSLSocket(host, port); + return ret(socket); + } + + @Override + public Socket createSocket(String host, int port, InetAddress inetAddress, int localPort) throws IOException, UnknownHostException + { + final GMSimpleSSLSocket socket = new GMSimpleSSLSocket(host, port, inetAddress, localPort); + return ret(socket); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int port) throws IOException + { + final GMSimpleSSLSocket socket = new GMSimpleSSLSocket(inetAddress, port); + return ret(socket); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int port, InetAddress localAddress, int localPort) throws IOException + { + final GMSimpleSSLSocket socket = new GMSimpleSSLSocket(inetAddress, port, localAddress, localPort); + return ret(socket); + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketWrap.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketWrap.java new file mode 100644 index 0000000000..3c64ae709d --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GMSimpleSSLSocketWrap.java @@ -0,0 +1,283 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.tls.Certificate; +import org.bouncycastle.tls.TlsClientProtocol; +import org.bouncycastle.tls.TlsProtocol; +import org.bouncycastle.tls.TlsServerProtocol; +import org.bouncycastle.tls.crypto.TlsCrypto; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +/** + * GMSSL Socket simple implement. + * + * + * @since 2021-03-16 13:18:06 + */ +public class GMSimpleSSLSocketWrap extends GMSimpleSSLSocket +{ + protected Socket wrapScoket = null; + protected boolean autoClose; + + public GMSimpleSSLSocketWrap(Socket wrapScoket, boolean autoClose) + { + super(); + this.wrapScoket = wrapScoket; + this.autoClose = autoClose; + } + + @Override + public void connect(SocketAddress endpoint) throws IOException + { + throw new SocketException("Wrapped socket should already be connected"); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException + { + throw new SocketException("Wrapped socket should already be connected"); + } + + @Override + public void bind(SocketAddress bindpoint) throws IOException + { + wrapScoket.bind(bindpoint); + } + + @Override + public InetAddress getInetAddress() + { + return wrapScoket.getInetAddress(); + } + + @Override + public InetAddress getLocalAddress() + { + return wrapScoket.getLocalAddress(); + } + + @Override + public int getPort() + { + return wrapScoket.getPort(); + } + + @Override + public int getLocalPort() + { + return wrapScoket.getLocalPort(); + } + + @Override + public SocketAddress getRemoteSocketAddress() + { + return wrapScoket.getRemoteSocketAddress(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return wrapScoket.getLocalSocketAddress(); + } + + @Override + public SocketChannel getChannel() + { + return wrapScoket.getChannel(); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException + { + wrapScoket.setTcpNoDelay(on); + } + + @Override + public boolean getTcpNoDelay() throws SocketException + { + return wrapScoket.getTcpNoDelay(); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException + { + wrapScoket.setSoLinger(on, linger); + } + + @Override + public int getSoLinger() throws SocketException + { + return wrapScoket.getSoLinger(); + } + + @Override + public void sendUrgentData(int data) throws IOException + { + wrapScoket.sendUrgentData(data); + } + + @Override + public void setOOBInline(boolean on) throws SocketException + { + wrapScoket.setOOBInline(on); + } + + @Override + public boolean getOOBInline() throws SocketException + { + return wrapScoket.getOOBInline(); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException + { + wrapScoket.setSoTimeout(timeout); + } + + @Override + public synchronized int getSoTimeout() throws SocketException + { + return wrapScoket.getSoTimeout(); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException + { + wrapScoket.setSendBufferSize(size); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException + { + return wrapScoket.getSendBufferSize(); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException + { + wrapScoket.setReceiveBufferSize(size); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException + { + return wrapScoket.getReceiveBufferSize(); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException + { + wrapScoket.setKeepAlive(on); + } + + @Override + public boolean getKeepAlive() throws SocketException + { + return wrapScoket.getKeepAlive(); + } + + @Override + public void setTrafficClass(int tc) throws SocketException + { + wrapScoket.setTrafficClass(tc); + } + + @Override + public int getTrafficClass() throws SocketException + { + return wrapScoket.getTrafficClass(); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException + { + wrapScoket.setReuseAddress(on); + } + + @Override + public boolean getReuseAddress() throws SocketException + { + return wrapScoket.getReuseAddress(); + } + + @Override + public void shutdownInput() throws IOException + { + wrapScoket.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException + { + wrapScoket.shutdownOutput(); + } + + @Override + public String toString() + { + return wrapScoket.toString(); + } + + @Override + public boolean isConnected() + { + return wrapScoket.isConnected(); + } + + @Override + public boolean isBound() + { + return wrapScoket.isBound(); + } + + @Override + public boolean isClosed() + { + return wrapScoket.isClosed(); + } + + @Override + public boolean isInputShutdown() + { + return wrapScoket.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() + { + return wrapScoket.isOutputShutdown(); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) + { + wrapScoket.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public void startHandshake() throws IOException + { + makeHandshake(wrapScoket.getInputStream(), wrapScoket.getOutputStream()); + } + @Override + public synchronized void close() throws IOException + { + super.close(); + if(autoClose) + { + wrapScoket.close(); + } + } + +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsClientProtocol.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsClientProtocol.java new file mode 100644 index 0000000000..b17ba2bf5b --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsClientProtocol.java @@ -0,0 +1,25 @@ +package org.bouncycastle.jsse.provider.gm; + + +import org.bouncycastle.tls.SecurityParameters; +import org.bouncycastle.tls.TlsClientProtocol; + +import java.io.InputStream; +import java.io.OutputStream; + +public class GmSimpleTlsClientProtocol extends TlsClientProtocol implements SecurityParameterProvider +{ + public GmSimpleTlsClientProtocol() + { + } + + public GmSimpleTlsClientProtocol(InputStream input, OutputStream output) + { + super(input, output); + } + + public SecurityParameters getSecurityParameters() + { + return super.getContext().getSecurityParameters(); + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsServerProtocol.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsServerProtocol.java new file mode 100644 index 0000000000..fc072669d9 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/GmSimpleTlsServerProtocol.java @@ -0,0 +1,25 @@ +package org.bouncycastle.jsse.provider.gm; + + +import org.bouncycastle.tls.SecurityParameters; +import org.bouncycastle.tls.TlsServerProtocol; + +import java.io.InputStream; +import java.io.OutputStream; + +public class GmSimpleTlsServerProtocol extends TlsServerProtocol implements SecurityParameterProvider +{ + public GmSimpleTlsServerProtocol() + { + } + + public GmSimpleTlsServerProtocol(InputStream input, OutputStream output) + { + super(input, output); + } + + public SecurityParameters getSecurityParameters() + { + return super.getContext().getSecurityParameters(); + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/gm/SecurityParameterProvider.java b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/SecurityParameterProvider.java new file mode 100644 index 0000000000..31cb351e16 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/gm/SecurityParameterProvider.java @@ -0,0 +1,9 @@ +package org.bouncycastle.jsse.provider.gm; + +import org.bouncycastle.tls.SecurityParameters; + + +public interface SecurityParameterProvider { + + SecurityParameters getSecurityParameters(); +} diff --git a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsKeyExchangeFactory.java b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsKeyExchangeFactory.java index 4c84a9a06b..d2991bee0a 100644 --- a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsKeyExchangeFactory.java +++ b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsKeyExchangeFactory.java @@ -91,4 +91,9 @@ public TlsKeyExchange createSRPKeyExchangeServer(int keyExchange, TlsSRPLoginPar { throw new TlsFatalAlert(AlertDescription.internal_error); } + + public TlsKeyExchange createSM2KeyExchange(int keyExchange) throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } } diff --git a/tls/src/main/java/org/bouncycastle/tls/CipherSuite.java b/tls/src/main/java/org/bouncycastle/tls/CipherSuite.java index 2d8ec979e5..f16e7316c7 100644 --- a/tls/src/main/java/org/bouncycastle/tls/CipherSuite.java +++ b/tls/src/main/java/org/bouncycastle/tls/CipherSuite.java @@ -444,4 +444,11 @@ public static boolean isSCSV(int cipherSuite) public static final int TLS_CHACHA20_POLY1305_SHA256 = 0x1303; public static final int TLS_AES_128_CCM_SHA256 = 0x1304; public static final int TLS_AES_128_CCM_8_SHA256 = 0x1305; + + /* + * GMT 0024-2014 + */ + public static final int GMSSL_ECC_SM4_SM3 = 0xe013; + + } diff --git a/tls/src/main/java/org/bouncycastle/tls/ClientCertificateType.java b/tls/src/main/java/org/bouncycastle/tls/ClientCertificateType.java index bfff66402b..4c1ed40f4a 100644 --- a/tls/src/main/java/org/bouncycastle/tls/ClientCertificateType.java +++ b/tls/src/main/java/org/bouncycastle/tls/ClientCertificateType.java @@ -19,4 +19,13 @@ public class ClientCertificateType public static final short ecdsa_sign = 64; public static final short rsa_fixed_ecdh = 65; public static final short ecdsa_fixed_ecdh = 66; + + /* + * GMT0024 has not mentioned + * + * Just specify a number here + */ + public static final short sm2_encrypt = 128; + + } diff --git a/tls/src/main/java/org/bouncycastle/tls/DefaultTlsKeyExchangeFactory.java b/tls/src/main/java/org/bouncycastle/tls/DefaultTlsKeyExchangeFactory.java index 949860cd1f..2ff10a2d85 100644 --- a/tls/src/main/java/org/bouncycastle/tls/DefaultTlsKeyExchangeFactory.java +++ b/tls/src/main/java/org/bouncycastle/tls/DefaultTlsKeyExchangeFactory.java @@ -89,4 +89,17 @@ public TlsKeyExchange createSRPKeyExchangeServer(int keyExchange, TlsSRPLoginPar { return new TlsSRPKeyExchange(keyExchange, loginParameters); } + + /** + * GMSSL ECC_SM4_SM3 suite key exchange + * + * @param keyExchange enum type + * @return SM2 key exchange object + * @throws IOException err + */ + @Override + public TlsKeyExchange createSM2KeyExchange(int keyExchange) throws IOException + { + return new TlsSM2KeyExchange(keyExchange); + } } diff --git a/tls/src/main/java/org/bouncycastle/tls/EncryptionAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/EncryptionAlgorithm.java index 847dba5ecb..73f8aebea5 100644 --- a/tls/src/main/java/org/bouncycastle/tls/EncryptionAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/EncryptionAlgorithm.java @@ -66,4 +66,9 @@ public class EncryptionAlgorithm * RFC 7905 */ public static final int CHACHA20_POLY1305 = 21; + + /* + * GMT 0024-2014 + */ + public static final int SM4_CBC = 31; } diff --git a/tls/src/main/java/org/bouncycastle/tls/HashAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/HashAlgorithm.java index 75332c0ed2..2ad2fea7df 100644 --- a/tls/src/main/java/org/bouncycastle/tls/HashAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/HashAlgorithm.java @@ -18,6 +18,12 @@ public class HashAlgorithm */ public static final short Intrinsic = 8; + /* + * GMT 0024-2014 No value is specified, + * so a value is randomly specified here to avoid conflicts + */ + public static final short sm3 = 20; + public static String getName(short hashAlgorithm) { switch (hashAlgorithm) @@ -38,6 +44,8 @@ public static String getName(short hashAlgorithm) return "sha512"; case Intrinsic: return "Intrinsic"; + case sm3: + return "sm3"; default: return "UNKNOWN"; } @@ -54,6 +62,7 @@ public static int getOutputSize(short hashAlgorithm) case sha224: return 28; case sha256: + case sm3: return 32; case sha384: return 48; @@ -79,6 +88,7 @@ public static boolean isRecognized(short hashAlgorithm) switch (hashAlgorithm) { case md5: + case sm3: case sha1: case sha224: case sha256: diff --git a/tls/src/main/java/org/bouncycastle/tls/KeyExchangeAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/KeyExchangeAlgorithm.java index bc58335ded..c285e7cfe8 100644 --- a/tls/src/main/java/org/bouncycastle/tls/KeyExchangeAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/KeyExchangeAlgorithm.java @@ -53,4 +53,9 @@ public class KeyExchangeAlgorithm * RFC 5489 */ public static final int ECDHE_PSK = 24; + + /* + * GMT 0024-2014 + */ + public static final int SM2 = 50; } diff --git a/tls/src/main/java/org/bouncycastle/tls/MACAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/MACAlgorithm.java index 5b556bbd5b..d1588740d5 100644 --- a/tls/src/main/java/org/bouncycastle/tls/MACAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/MACAlgorithm.java @@ -21,6 +21,12 @@ public class MACAlgorithm public static final int hmac_sha384 = 4; public static final int hmac_sha512 = 5; + /* + * GMT 0024-2014 No value is specified, + * so a value is randomly specified here to avoid conflicts + */ + public static final int hmac_sm3 = 20; + public static String getName(int macAlgorithm) { switch (macAlgorithm) @@ -37,6 +43,8 @@ public static String getName(int macAlgorithm) return "hmac_sha384"; case hmac_sha512: return "hmac_sha512"; + case hmac_sm3: + return "hmac_sm3"; default: return "UNKNOWN"; } @@ -56,6 +64,7 @@ public static boolean isHMAC(int macAlgorithm) case hmac_sha256: case hmac_sha384: case hmac_sha512: + case hmac_sm3: return true; default: return false; diff --git a/tls/src/main/java/org/bouncycastle/tls/PRFAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/PRFAlgorithm.java index e42bb83a71..58075861fc 100644 --- a/tls/src/main/java/org/bouncycastle/tls/PRFAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/PRFAlgorithm.java @@ -14,6 +14,7 @@ public class PRFAlgorithm public static final int tls_prf_sha384 = 3; public static final int tls13_hkdf_sha256 = 4; public static final int tls13_hkdf_sha384 = 5; + public static final int gmssl11_prf_sm3 = 6; public static String getName(int prfAlgorithm) { @@ -31,6 +32,8 @@ public static String getName(int prfAlgorithm) return "tls13_hkdf_sha256"; case tls13_hkdf_sha384: return "tls13_hkdf_sha384"; + case gmssl11_prf_sm3: + return "gmssl11_prf_sm3"; default: return "UNKNOWN"; } diff --git a/tls/src/main/java/org/bouncycastle/tls/ProtocolVersion.java b/tls/src/main/java/org/bouncycastle/tls/ProtocolVersion.java index 2a4ab5c43f..b9510bc4f0 100644 --- a/tls/src/main/java/org/bouncycastle/tls/ProtocolVersion.java +++ b/tls/src/main/java/org/bouncycastle/tls/ProtocolVersion.java @@ -14,6 +14,9 @@ public final class ProtocolVersion public static final ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0"); public static final ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2"); + public static final ProtocolVersion GMSSLv11 = new ProtocolVersion(0x0101, "GMSSL 1.1"); + + static final ProtocolVersion CLIENT_GM_SUPPORTED_TLS = GMSSLv11; static final ProtocolVersion CLIENT_EARLIEST_SUPPORTED_DTLS = DTLSv10; static final ProtocolVersion CLIENT_EARLIEST_SUPPORTED_TLS = SSLv3; static final ProtocolVersion CLIENT_LATEST_SUPPORTED_DTLS = DTLSv12; @@ -142,6 +145,9 @@ static boolean isSupportedTLSVersionClient(ProtocolVersion version) int fullVersion = version.getFullVersion(); + if(fullVersion == CLIENT_GM_SUPPORTED_TLS.getFullVersion()){ + return true; + } return fullVersion >= CLIENT_EARLIEST_SUPPORTED_TLS.getFullVersion() && fullVersion <= CLIENT_LATEST_SUPPORTED_TLS.getFullVersion(); } @@ -155,6 +161,9 @@ static boolean isSupportedTLSVersionServer(ProtocolVersion version) int fullVersion = version.getFullVersion(); + if(fullVersion == CLIENT_GM_SUPPORTED_TLS.getFullVersion()){ + return true; + } return fullVersion >= SERVER_EARLIEST_SUPPORTED_TLS.getFullVersion() && fullVersion <= SERVER_LATEST_SUPPORTED_TLS.getFullVersion(); } @@ -218,6 +227,10 @@ public boolean isDTLS() return getMajorVersion() == 0xFE; } + public boolean isGMSSL(){ + return getMajorVersion() == 0x01; + } + public boolean isSSL() { return this == SSLv3; @@ -225,7 +238,9 @@ public boolean isSSL() public boolean isTLS() { - return getMajorVersion() == 0x03; + final int majorVersion = getMajorVersion(); + // GMSSL is also a variants TLS + return majorVersion == 0x03 || majorVersion == 0x01; } public ProtocolVersion getEquivalentTLSVersion() @@ -240,6 +255,7 @@ public ProtocolVersion getEquivalentTLSVersion() case 0xFD: return TLSv12; default: return null; } + case 0x01: return GMSSLv11; default: return null; } } @@ -347,6 +363,15 @@ public static ProtocolVersion get(int major, int minor) { switch (major) { + case 0x01: + { + switch (minor) + { + case 0x01: + return GMSSLv11; + } + return getUnknownVersion(major, minor, "GMSSL"); + } case 0x03: { switch (minor) diff --git a/tls/src/main/java/org/bouncycastle/tls/SignatureAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/SignatureAlgorithm.java index 9e8b7382be..561e28b3b5 100644 --- a/tls/src/main/java/org/bouncycastle/tls/SignatureAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/SignatureAlgorithm.java @@ -10,6 +10,7 @@ public class SignatureAlgorithm public static final short dsa = 2; public static final short ecdsa = 3; + /* * RFC 8422 */ @@ -33,6 +34,13 @@ public class SignatureAlgorithm public static final short ecdsa_brainpoolP384r1tls13_sha384 = 27; public static final short ecdsa_brainpoolP512r1tls13_sha512 = 28; + + /* + * GMSSL GMT 0009-2012 + */ + public static final short sm2 = 200; + + public static short getClientCertificateType(short signatureAlgorithm) { switch (signatureAlgorithm) @@ -52,6 +60,7 @@ public static short getClientCertificateType(short signatureAlgorithm) case SignatureAlgorithm.ecdsa: case SignatureAlgorithm.ed25519: case SignatureAlgorithm.ed448: + case SignatureAlgorithm.sm2: return ClientCertificateType.ecdsa_sign; // NOTE: Only valid from TLS 1.3, where ClientCertificateType is not used @@ -97,6 +106,8 @@ public static String getName(short signatureAlgorithm) return "ecdsa_brainpoolP384r1tls13_sha384"; case ecdsa_brainpoolP512r1tls13_sha512: return "ecdsa_brainpoolP512r1tls13_sha512"; + case sm2: + return "sm2"; default: return "UNKNOWN"; } diff --git a/tls/src/main/java/org/bouncycastle/tls/SignatureAndHashAlgorithm.java b/tls/src/main/java/org/bouncycastle/tls/SignatureAndHashAlgorithm.java index c3227f7662..8cf08750d0 100644 --- a/tls/src/main/java/org/bouncycastle/tls/SignatureAndHashAlgorithm.java +++ b/tls/src/main/java/org/bouncycastle/tls/SignatureAndHashAlgorithm.java @@ -29,6 +29,7 @@ public class SignatureAndHashAlgorithm SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384); public static final SignatureAndHashAlgorithm ecdsa_brainpoolP512r1tls13_sha512 = createInstanceIntrinsic( SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512); + public static final SignatureAndHashAlgorithm sm2 = createInstanceIntrinsic(SignatureAlgorithm.sm2); public static SignatureAndHashAlgorithm getInstance(short hashAlgorithm, short signatureAlgorithm) { @@ -67,6 +68,8 @@ public static SignatureAndHashAlgorithm getInstanceIntrinsic(short signatureAlgo return ecdsa_brainpoolP384r1tls13_sha384; case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512: return ecdsa_brainpoolP512r1tls13_sha512; + case SignatureAlgorithm.sm2: + return sm2; default: return new SignatureAndHashAlgorithm(HashAlgorithm.Intrinsic, signatureAlgorithm); } diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java b/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java index 2b05fa5415..268b23105d 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java @@ -1612,6 +1612,10 @@ protected void sendClientHello() // TODO[tls13] Prevent offering SSLv3 AND TLSv13? recordStream.setWriteVersion(ProtocolVersion.SSLv3); } + else if (ProtocolVersion.contains(tlsClientContext.getClientSupportedVersions(), ProtocolVersion.GMSSLv11)) + { + recordStream.setWriteVersion(ProtocolVersion.GMSSLv11); + } else { recordStream.setWriteVersion(ProtocolVersion.TLSv10); diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsKeyExchangeFactory.java b/tls/src/main/java/org/bouncycastle/tls/TlsKeyExchangeFactory.java index 81197f23ce..ae82c9fbd4 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsKeyExchangeFactory.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsKeyExchangeFactory.java @@ -44,4 +44,14 @@ TlsKeyExchange createSRPKeyExchangeClient(int keyExchange, TlsSRPIdentity srpIde TlsKeyExchange createSRPKeyExchangeServer(int keyExchange, TlsSRPLoginParameters loginParameters) throws IOException; + + /** + * GMSSL ECC_SM4_SM3 suite key exchange + * + * @param keyExchange enum type + * @return SM2 key exchange object + * @throws IOException err + */ + TlsKeyExchange createSM2KeyExchange(int keyExchange) + throws IOException; } diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsSM2KeyExchange.java b/tls/src/main/java/org/bouncycastle/tls/TlsSM2KeyExchange.java new file mode 100644 index 0000000000..ba959707c0 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/TlsSM2KeyExchange.java @@ -0,0 +1,220 @@ +package org.bouncycastle.tls; + +import org.bouncycastle.tls.crypto.TlsCertificate; +import org.bouncycastle.tls.crypto.TlsCryptoParameters; +import org.bouncycastle.tls.crypto.TlsSecret; +import org.bouncycastle.tls.crypto.TlsVerifier; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * GMSSL SM2 exchange. + * + * + */ +public class TlsSM2KeyExchange extends AbstractTlsKeyExchange +{ + + + private static int checkKeyExchange(int keyExchange) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.SM2: + return keyExchange; + default: + throw new IllegalArgumentException("unsupported key exchange algorithm"); + } + } + + protected TlsCredentialedDecryptor serverDecryptor = null; + protected TlsCredentialedSigner serverSigner = null; + /** + * first cert of certificate list + * use to sign server side key exchange message + *

+ * digitally-signed struct + * { + * opaque client_random[32]; + * opaque server_random[32]; + * opaque ASN.1Cert<1..2^24-1> + * } signed params + */ + protected TlsCertificate serverSigCertificate; + + /** + * second cert of certificate list + * use to encrypt client generate preMasterSecret. + */ + protected TlsCertificate serverEncCertificate; + + protected TlsSecret preMasterSecret; + + public TlsSM2KeyExchange(int keyExchange) + { + super(checkKeyExchange(keyExchange)); + } + + public void skipServerCredentials() throws IOException + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public void processServerCredentials(TlsCredentials serverCredentials) throws IOException + { + if(serverCredentials instanceof TlsCredentialedDecryptor && serverCredentials instanceof TlsCredentialedSigner) + { + serverSigner = (TlsCredentialedSigner) serverCredentials; + serverDecryptor = (TlsCredentialedDecryptor) serverCredentials; + final TlsCertificate[] certificateList = serverCredentials.getCertificate().getCertificateList(); + if(certificateList == null || certificateList.length < 2) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + // get certificate + serverSigCertificate = certificateList[0]; + serverEncCertificate = certificateList[1]; + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + @Override + public byte[] generateServerKeyExchange() throws IOException + { + // build key exchange message plaintext, struct see #buildServerKeyExchangeParams method. + final byte[] plaintext = buildServerKeyExchangeParams(); + final byte[] signature = serverSigner.generateRawSignature(plaintext); + ByteArrayOutputStream bout = new ByteArrayOutputStream(signature.length+2); + TlsUtils.writeOpaque16(signature,bout); + return bout.toByteArray(); + } + + @Override + public void processServerKeyExchange(InputStream input) throws IOException + { + + final int n = TlsUtils.readUint16(input); + final byte[] signature = TlsUtils.readFully(n, input); + // build KeyExchangeParams plaintext. + byte[] plaintext = buildServerKeyExchangeParams(); + final TlsVerifier verifier = serverSigCertificate.createVerifier(SignatureAlgorithm.sm2); + + DigitallySigned digitallySigned = new DigitallySigned(SignatureAndHashAlgorithm.sm2, signature); + final boolean pass = verifier.verifyRawSignature(digitallySigned, plaintext); + if(!pass) + { + throw new TlsFatalAlertReceived(AlertDescription.illegal_parameter); + } +// this.serverSigCertificate.createVerifier() + return; + } + + public void processServerCertificate(Certificate serverCertificate) throws IOException + { + // GMSSL has two certificates, first is for signing the second is for encryption + if(serverCertificate.getLength() < 2) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + // sign cert + this.serverSigCertificate = serverCertificate.getCertificateAt(0).useInRole(ConnectionEnd.server, KeyExchangeAlgorithm.SM2); + // encrypt cert + this.serverEncCertificate = serverCertificate.getCertificateAt(1).useInRole(ConnectionEnd.server, KeyExchangeAlgorithm.SM2); + } + + public short[] getClientCertificateTypes() + { + return new short[]{ClientCertificateType.sm2_encrypt}; + } + + public void processClientCredentials(TlsCredentials clientCredentials) throws IOException + { + + } + + /** + * generate preMasterSecret then use enc certificate public key + * enc preMasterSecret + * + * @param output + * @throws IOException + */ + public void generateClientKeyExchange(OutputStream output) throws IOException + { + /* + * GMSSL PreMasterSecret struct same with rsaPreMasterSecret + * + * struct + * { + * ProtocolVersion client_version; + * opaque random[46]; + * } PreMasterSecret + */ + this.preMasterSecret = context.getCrypto().generateRSAPreMasterSecret(context.getClientVersion()); + // add BcGmsslEncryptor to support encrypt preMasterSecret + byte[] encryptedPreMasterSecret = preMasterSecret.encrypt(serverEncCertificate); + TlsUtils.writeEncryptedPMS(context, encryptedPreMasterSecret, output); + } + + public void processClientKeyExchange(InputStream input) throws IOException + { + byte[] encryptedPreMasterSecret = TlsUtils.readEncryptedPMS(context, input); + this.preMasterSecret = serverDecryptor.decrypt(new TlsCryptoParameters(context), encryptedPreMasterSecret); + } + + public TlsSecret generatePreMasterSecret() throws IOException + { + TlsSecret tmp = this.preMasterSecret; + this.preMasterSecret = null; + return tmp; + } + + /** + * build Server side Key Exchange Params plaintext. + * @return params plaintext + * @throws IOException + */ + private byte[] buildServerKeyExchangeParams() throws IOException + { + /* + * SM2_SM4_SM3 suite ServerKeyExchange message struct + * + * GM0009-2012: SM2 is called ECC, + * + * enum {ECDHE,ECC,IBSDH,IBC,RSA} KeyExchangeAlgorithm; + * struct + * { + * select(KeyExchangeAlgorithm) { + * case ECC: + * digitally-signed struct + * { + * opaque client_random[32]; + * opaque server_random[32]; + * opaque ASN.1Cert<1..2^24-1> + * } signed_params + * } + * } ServerKeyExchange; + * + * the ASN.1Cert field is encrypt certificate + */ + final SecurityParameters securityParameters = context.getSecurityParametersHandshake(); + final byte[] clientRandom = securityParameters.getClientRandom(); + final byte[] serverRandom = securityParameters.getServerRandom(); + final byte[] encCert = serverEncCertificate.getEncoded(); + int totalSize = clientRandom.length + serverRandom.length + 3 + encCert.length; + byte[] plaintext = new byte[totalSize]; + System.arraycopy(clientRandom, 0, plaintext, 0, 32); + System.arraycopy(serverRandom, 0, plaintext, 32, 32); + plaintext[64] = (byte) (0xff & (encCert.length >> 16)); + plaintext[65] = (byte) (0xff & (encCert.length >> 8)); + plaintext[66] = (byte) (0xff & (encCert.length)); + System.arraycopy(encCert, 0, plaintext, 67, encCert.length); + return plaintext; + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java b/tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java index b60f146ee6..f35ba06a47 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsServerProtocol.java @@ -174,7 +174,7 @@ protected ServerHello generate13ServerHello(ClientHello clientHello, boolean aft /* * TODO[tls13] Confirm fields in the ClientHello haven't changed - * + * * RFC 8446 4.1.2 [..] when the server has responded to its ClientHello with a * HelloRetryRequest [..] the client MUST send the same ClientHello without * modification, except as follows: [key_share, early_data, cookie, pre_shared_key, @@ -223,7 +223,7 @@ protected ServerHello generate13ServerHello(ClientHello clientHello, boolean aft /* * NOTE: Currently no server support for session resumption - * + * * If adding support, ensure securityParameters.tlsUnique is set to the localVerifyData, but * ONLY when extended_master_secret has been negotiated (otherwise NULL). */ @@ -334,7 +334,7 @@ protected ServerHello generate13ServerHello(ClientHello clientHello, boolean aft /* * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions. - * + * * OCSP information is carried in an extension for a CertificateEntry. */ securityParameters.statusRequestVersion = clientHelloExtensions.containsKey(TlsExtensionsUtils.EXT_status_request) @@ -344,7 +344,7 @@ protected ServerHello generate13ServerHello(ClientHello clientHello, boolean aft { int namedGroup = clientShare.getNamedGroup(); - + TlsAgreement agreement; if (NamedGroup.refersToASpecificCurve(namedGroup)) { @@ -390,7 +390,6 @@ protected ServerHello generateServerHello(ClientHello clientHello) throws IOExce this.offeredCipherSuites = clientHello.getCipherSuites(); - SecurityParameters securityParameters = tlsServerContext.getSecurityParametersHandshake(); tlsServerContext.setClientSupportedVersions( @@ -399,22 +398,37 @@ protected ServerHello generateServerHello(ClientHello clientHello) throws IOExce ProtocolVersion clientVersion = clientLegacyVersion; if (null == tlsServerContext.getClientSupportedVersions()) { - if (clientVersion.isLaterVersionOf(ProtocolVersion.TLSv12)) + if(clientVersion.getEquivalentTLSVersion() == ProtocolVersion.GMSSLv11) { - clientVersion = ProtocolVersion.TLSv12; + tlsServerContext.setClientSupportedVersions(new ProtocolVersion[]{clientVersion}); + } + else + { + if (clientVersion.isLaterVersionOf(ProtocolVersion.TLSv12)) + { + clientVersion = ProtocolVersion.TLSv12; + } + + tlsServerContext.setClientSupportedVersions(clientVersion.downTo(ProtocolVersion.SSLv3)); } - tlsServerContext.setClientSupportedVersions(clientVersion.downTo(ProtocolVersion.SSLv3)); } else { clientVersion = ProtocolVersion.getLatestTLS(tlsServerContext.getClientSupportedVersions()); } + // check server client is support client versions + if(!ProtocolVersion.contains(tlsServer.getProtocolVersions(), clientVersion)) + { + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + // Set the legacy_record_version to use for early alerts recordStream.setWriteVersion(clientVersion); - if (!ProtocolVersion.SERVER_EARLIEST_SUPPORTED_TLS.isEqualOrEarlierVersionOf(clientVersion)) + if (!ProtocolVersion.SERVER_EARLIEST_SUPPORTED_TLS.isEqualOrEarlierVersionOf(clientVersion) + && !ProtocolVersion.CLIENT_GM_SUPPORTED_TLS.isEqualOrEarlierVersionOf(clientVersion)) { throw new TlsFatalAlert(AlertDescription.protocol_version); } @@ -721,7 +735,7 @@ protected void handle13HandshakeMessage(short type, HandshakeMessageInput buf) { /* * TODO[tls13] Abbreviated handshakes (PSK resumption) - * + * * NOTE: No CertificateRequest, Certificate, CertificateVerify messages, but client * might now send EndOfEarlyData after receiving server Finished message. */ @@ -1099,7 +1113,7 @@ else if (TlsUtils.isTLSv12(tlsServerContext)) /* * RFC 5246 If no suitable certificate is available, the client MUST send a * certificate message containing no certificates. - * + * * NOTE: In previous RFCs, this was SHOULD instead of MUST. */ throw new TlsFatalAlert(AlertDescription.unexpected_message); diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java b/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java index d71da52937..5c3dbdf398 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java @@ -316,6 +316,10 @@ public static boolean isTLSv13(ProtocolVersion version) return ProtocolVersion.TLSv13.isEqualOrEarlierVersionOf(version.getEquivalentTLSVersion()); } + public static boolean isGMSSLv11(ProtocolVersion version) { + return ProtocolVersion.GMSSLv11.isEqualOrEarlierVersionOf(version.getEquivalentTLSVersion()); + } + public static boolean isTLSv13(TlsContext context) { return isTLSv13(context.getServerVersion()); @@ -1793,6 +1797,8 @@ public static short getHashAlgorithmForHMACAlgorithm(int macAlgorithm) return HashAlgorithm.sha384; case MACAlgorithm.hmac_sha512: return HashAlgorithm.sha512; + case MACAlgorithm.hmac_sm3: + return HashAlgorithm.sm3; default: throw new IllegalArgumentException("specified MACAlgorithm not an HMAC: " + MACAlgorithm.getText(macAlgorithm)); } @@ -1811,6 +1817,8 @@ public static short getHashAlgorithmForPRFAlgorithm(int prfAlgorithm) case PRFAlgorithm.tls_prf_sha384: case PRFAlgorithm.tls13_hkdf_sha384: return HashAlgorithm.sha384; + case PRFAlgorithm.gmssl11_prf_sm3: + return HashAlgorithm.sm3; default: throw new IllegalArgumentException("unknown PRFAlgorithm: " + PRFAlgorithm.getText(prfAlgorithm)); } @@ -1841,6 +1849,7 @@ static int getPRFAlgorithm(SecurityParameters securityParameters, int cipherSuit { ProtocolVersion negotiatedVersion = securityParameters.getNegotiatedVersion(); + final boolean isGMSSLv11 = isGMSSLv11(negotiatedVersion); final boolean isTLSv13 = isTLSv13(negotiatedVersion); final boolean isTLSv12Exactly = !isTLSv13 && isTLSv12(negotiatedVersion); final boolean isSSL = negotiatedVersion.isSSL(); @@ -2090,6 +2099,15 @@ static int getPRFAlgorithm(SecurityParameters securityParameters, int cipherSuit return PRFAlgorithm.tls_prf_legacy; } + case CipherSuite.GMSSL_ECC_SM4_SM3: + { + if (isGMSSLv11) + { + return PRFAlgorithm.gmssl11_prf_sm3; + } + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + default: { if (isTLSv13) @@ -2880,6 +2898,9 @@ public static int getEncryptionAlgorithm(int cipherSuite) case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA: return EncryptionAlgorithm.SEED_CBC; + case CipherSuite.GMSSL_ECC_SM4_SM3: + return EncryptionAlgorithm.SM4_CBC; + default: return -1; } @@ -2914,6 +2935,7 @@ public static int getEncryptionAlgorithmType(int encryptionAlgorithm) case EncryptionAlgorithm.CAMELLIA_128_CBC: case EncryptionAlgorithm.CAMELLIA_256_CBC: case EncryptionAlgorithm.SEED_CBC: + case EncryptionAlgorithm.SM4_CBC: return CipherType.block; case EncryptionAlgorithm.NULL: @@ -3257,6 +3279,9 @@ public static int getKeyExchangeAlgorithm(int cipherSuite) case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: return KeyExchangeAlgorithm.SRP_RSA; + case CipherSuite.GMSSL_ECC_SM4_SM3: + return KeyExchangeAlgorithm.SM2; + default: return -1; } @@ -3577,6 +3602,9 @@ public static int getMACAlgorithm(int cipherSuite) case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384: return MACAlgorithm.hmac_sha384; + case CipherSuite.GMSSL_ECC_SM4_SM3: + return MACAlgorithm.hmac_sm3; + default: return -1; } @@ -3773,6 +3801,9 @@ public static ProtocolVersion getMinimumVersion(int cipherSuite) case CipherSuite.TLS_RSA_WITH_NULL_SHA256: return ProtocolVersion.TLSv12; + case CipherSuite.GMSSL_ECC_SM4_SM3: + return ProtocolVersion.GMSSLv11; + default: return ProtocolVersion.SSLv3; } @@ -4191,7 +4222,8 @@ public static boolean isSupportedKeyExchange(TlsCrypto crypto, int keyExchangeAl case KeyExchangeAlgorithm.SRP_RSA: return crypto.hasSRPAuthentication() && hasAnyRSASigAlgs(crypto); - + case KeyExchangeAlgorithm.SM2: + return true; default: return false; } @@ -4269,6 +4301,9 @@ private static TlsKeyExchange createKeyExchangeClient(TlsClient client, int keyE return factory.createSRPKeyExchangeClient(keyExchange, client.getSRPIdentity(), client.getSRPConfigVerifier()); + case KeyExchangeAlgorithm.SM2: + return factory.createSM2KeyExchange(keyExchange); + default: /* * Note: internal error here; the TlsProtocol implementation verifies that the @@ -4326,6 +4361,9 @@ private static TlsKeyExchange createKeyExchangeServer(TlsServer server, int keyE case KeyExchangeAlgorithm.SRP_RSA: return factory.createSRPKeyExchangeServer(keyExchange, server.getSRPLoginParameters()); + case KeyExchangeAlgorithm.SM2: + return factory.createSM2KeyExchange(keyExchange); + default: /* * Note: internal error here; the TlsProtocol implementation verifies that the @@ -5205,7 +5243,7 @@ static TlsCredentials validateCredentials(TlsCredentials credentials) throws IOE count += (credentials instanceof TlsCredentialedAgreement) ? 1 : 0; count += (credentials instanceof TlsCredentialedDecryptor) ? 1 : 0; count += (credentials instanceof TlsCredentialedSigner) ? 1 : 0; - if (count != 1) + if (count < 1) { throw new TlsFatalAlert(AlertDescription.internal_error); } diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcGmsslEncryptor.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcGmsslEncryptor.java new file mode 100644 index 0000000000..dffa954dd3 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcGmsslEncryptor.java @@ -0,0 +1,46 @@ +package org.bouncycastle.tls.crypto.impl; + +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.tls.AlertDescription; +import org.bouncycastle.tls.TlsFatalAlert; +import org.bouncycastle.tls.crypto.impl.bc.SM2Cipher; + +import java.io.IOException; +import java.security.SecureRandom; + +/** + * GMSSL basic chinese GMT 0009-2012 + * + * + * @since 2021-03-10 13:56:20 + */ +public class BcGmsslEncryptor implements TlsEncryptor +{ + + + private final ParametersWithRandom keyParameters; + + public BcGmsslEncryptor(ECPublicKeyParameters keyParameters, SecureRandom secureRandom) + { + this.keyParameters = new ParametersWithRandom(keyParameters, secureRandom); + + } + + public byte[] encrypt(byte[] input, int inOff, int length) throws IOException + { + try + { + SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2); + engine.init(true, keyParameters); + byte[] c1c3c2 = engine.processBlock(input, inOff, length); + return SM2Cipher.fromC1C3C2(c1c3c2).getEncoded(); + } + catch (InvalidCipherTextException e) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcTlsRSAEncryptor.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcTlsRSAEncryptor.java new file mode 100644 index 0000000000..072a88b601 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/BcTlsRSAEncryptor.java @@ -0,0 +1,46 @@ +package org.bouncycastle.tls.crypto.impl; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.encodings.PKCS1Encoding; +import org.bouncycastle.crypto.engines.RSABlindedEngine; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.tls.AlertDescription; +import org.bouncycastle.tls.TlsFatalAlert; + +import java.io.IOException; +import java.security.SecureRandom; + +public class BcTlsRSAEncryptor + implements TlsEncryptor +{ + + private final SecureRandom secureRandom; + private CipherParameters pubKeyRSA; + + public BcTlsRSAEncryptor(RSAKeyParameters pubKeyRSA, SecureRandom secureRandom) + { + this.pubKeyRSA = pubKeyRSA; + this.secureRandom = secureRandom; + + } + + public byte[] encrypt(byte[] input, int inOff, int length) + throws IOException + { + try + { + PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine()); + encoding.init(true, new ParametersWithRandom(pubKeyRSA, secureRandom)); + return encoding.processBlock(input, inOff, length); + } + catch (InvalidCipherTextException e) + { + /* + * This should never happen, only during decryption. + */ + throw new TlsFatalAlert(AlertDescription.internal_error, e); + } + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsBlockCipher.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsBlockCipher.java index 5000b2bb99..d700d2e745 100644 --- a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsBlockCipher.java +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsBlockCipher.java @@ -28,6 +28,7 @@ public class TlsBlockCipher protected final boolean useExplicitIV; protected final boolean acceptExtraPadding; protected final boolean useExtraPadding; + protected final boolean useGMSSL; protected final TlsBlockCipherImpl decryptCipher, encryptCipher; protected final TlsSuiteMac readMac, writeMac; @@ -49,6 +50,7 @@ public TlsBlockCipher(TlsCrypto crypto, TlsCryptoParameters cryptoParams, TlsBlo this.encryptThenMAC = securityParameters.isEncryptThenMAC(); this.useExplicitIV = TlsImplUtils.isTLSv11(negotiatedVersion); + this.useGMSSL = TlsUtils.isGMSSLv11(negotiatedVersion); this.acceptExtraPadding = !negotiatedVersion.isSSL(); @@ -205,7 +207,7 @@ public TlsEncodeResult encodePlaintext(long seqNo, short contentType, ProtocolVe } int totalSize = len + macSize + padding_length; - if (useExplicitIV) + if (useExplicitIV || useGMSSL) { totalSize += blockSize; } @@ -221,6 +223,29 @@ public TlsEncodeResult encodePlaintext(long seqNo, short contentType, ProtocolVe outOff += blockSize; } + if (useGMSSL) + { + /* + * GMSSL GenericBlockCipher struct same as RFC5246 TLS 1.2 + * struct { + * opaque IV[SecurityParameters.record_iv_length]; + * block-ciphered struct { + * opaque content[TLSCompressed.length]; + * opaque MAC[SecurityParameters.mac_length]; + * uint8 padding[GenericBlockCipher.padding_length]; + * uint8 padding_length; + * }; + * } GenericBlockCipher; + */ + byte[] explicitIV = cryptoParams.getNonceGenerator().generateNonce(blockSize); + System.arraycopy(explicitIV, 0, outBuf, outOff, blockSize); + // set cipher with new iv. + encryptCipher.init(explicitIV, 0, blockSize); + // GMSSL use explicit IV put after header, this part not be encrypted. + headerAllocation += blockSize; + outOff += blockSize; + } + System.arraycopy(plaintext, offset, outBuf, outOff, len); outOff += len; @@ -311,6 +336,18 @@ public TlsDecodeResult decodeCiphertext(long seqNo, short recordType, ProtocolVe } } + if(useGMSSL) + { + // Get explicit IV from begin of message + byte[] explicitIV = new byte[blockSize]; + System.arraycopy(ciphertext, offset, explicitIV, 0, blockSize); + // set explicit IV to decrypt cipher + decryptCipher.init(explicitIV, 0, blockSize); + // encrypted part does not include iv + offset += blockSize; + blocks_length -= blockSize; + } + decryptCipher.doFinal(ciphertext, offset, blocks_length, ciphertext, offset); if (useExplicitIV) diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcGMSSLCredentials.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcGMSSLCredentials.java new file mode 100644 index 0000000000..f004ac9de0 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcGMSSLCredentials.java @@ -0,0 +1,119 @@ +package org.bouncycastle.tls.crypto.impl.bc; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.tls.*; +import org.bouncycastle.tls.crypto.TlsCryptoParameters; +import org.bouncycastle.tls.crypto.TlsSecret; +import org.bouncycastle.tls.crypto.TlsStreamSigner; + +import java.io.IOException; +import java.security.Signature; + +/** + * GMSSL need two certificate + * one for sign, one for encrypt + * so we need provider Signer and Decryptor both + * + * + * @since 2021-03-12 12:10:31 + */ +public class BcGMSSLCredentials implements TlsCredentialedSigner, TlsCredentialedDecryptor +{ + private BcTlsCrypto crypto; + /* + * first cert for sign, second sert for encrypt + */ + private Certificate certList; + + private AsymmetricKeyParameter signKey; + private AsymmetricKeyParameter encKey; + private Signature rawSigner; + + public BcGMSSLCredentials(BcTlsCrypto crypto, Certificate certList, AsymmetricKeyParameter signKey, AsymmetricKeyParameter encKey) + { + if(certList.getLength() < 2) + { + throw new IllegalArgumentException("GMSSL need two certificate, first one for sign second one for encrypt."); + } + + this.crypto = crypto; + this.certList = certList; + this.signKey = signKey; + this.encKey = encKey; + } + + /** + * Use encrypt key decrypt ciphertext + * + * @param cryptoParams the parameters to use for the decryption. + * @param ciphertext the cipher text containing the secret. + * @return + * @throws IOException + */ + public TlsSecret decrypt(TlsCryptoParameters cryptoParams, byte[] ciphertext) throws IOException + { + try + { + // Parser ciphertext as ASN.1 SM2Cipher object. + final SM2Cipher sm2Cipher = SM2Cipher.getInstance(ciphertext); + byte[] c1c3c2 = sm2Cipher.convertC1C3C2(); + SM2Engine engine = new SM2Engine(SM2Engine.Mode.C1C3C2); + engine.init(false, encKey); + byte[] preMasterSecret = engine.processBlock(c1c3c2, 0, c1c3c2.length); + return new BcTlsSecret(crypto, preMasterSecret); + } catch (Exception e) + { + throw new TlsFatalAlertReceived(AlertDescription.illegal_parameter); + } + } + + /** + * Use sign private key sign + * + * @param hash a message digest calculated across the message the signature is to apply to. + * @return signature + * @throws IOException + */ + public byte[] generateRawSignature(byte[] hash) throws IOException + { + try + { + final ParametersWithRandom prvKey = new ParametersWithRandom(signKey, crypto.getSecureRandom()); + SM2Signer sm2Signer = new SM2Signer(); + sm2Signer.init(true, prvKey); + sm2Signer.update(hash, 0, hash.length); + return sm2Signer.generateSignature(); + } + catch (CryptoException e) + { + e.printStackTrace(); + throw new TlsFatalAlertReceived(AlertDescription.illegal_parameter); + } + } + + public SignatureAndHashAlgorithm getSignatureAndHashAlgorithm() + { + return SignatureAndHashAlgorithm.sm2; + } + + + /** + * GMSSL not support Stream mode + * + * @return null + * @throws IOException not happen + */ + public TlsStreamSigner getStreamSigner() throws IOException + { + return null; + } + + public Certificate getCertificate() + { + return certList; + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCertificate.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCertificate.java index 0798f8df22..99631cbc5a 100644 --- a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCertificate.java +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCertificate.java @@ -118,6 +118,9 @@ public TlsVerifier createVerifier(short signatureAlgorithm) throws IOException validateRSA_PSS_PSS(signatureAlgorithm); return new BcTlsRSAPSSVerifier(crypto, getPubKeyRSA(), signatureAlgorithm); + case SignatureAlgorithm.sm2: + return new BcTlsSM2Verifier(crypto, getPubKeyEC()); + default: throw new TlsFatalAlert(AlertDescription.certificate_unknown); } @@ -279,6 +282,24 @@ public RSAKeyParameters getPubKeyRSA() throws IOException } } + /** + * Get public key from sm2 certificate + * @return sm2 public key + * @throws IOException err + */ + public ECPublicKeyParameters getPubKeySM2() throws IOException + { + try + { + return (ECPublicKeyParameters)getPublicKey(); + } + catch (ClassCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + public boolean supportsSignatureAlgorithm(short signatureAlgorithm) throws IOException { return supportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.digitalSignature); @@ -321,6 +342,12 @@ public TlsCertificate useInRole(int connectionEnd, int keyExchangeAlgorithm) thr this.pubKeyRSA = getPubKeyRSA(); return this; } + case KeyExchangeAlgorithm.SM2: + { + // validateKeyUsage(KeyUsage.keyEncipherment); + pubKeyEC = getPubKeySM2(); + return this; + } } } diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCrypto.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCrypto.java index 2352200a02..956ac76cf5 100644 --- a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCrypto.java +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsCrypto.java @@ -22,25 +22,14 @@ import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.digests.SHA384Digest; import org.bouncycastle.crypto.digests.SHA512Digest; -import org.bouncycastle.crypto.encodings.PKCS1Encoding; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.engines.ARIAEngine; -import org.bouncycastle.crypto.engines.CamelliaEngine; -import org.bouncycastle.crypto.engines.DESedeEngine; -import org.bouncycastle.crypto.engines.RC4Engine; -import org.bouncycastle.crypto.engines.RSABlindedEngine; -import org.bouncycastle.crypto.engines.SEEDEngine; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.engines.*; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.modes.CCMBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; -import org.bouncycastle.crypto.params.ParametersWithIV; -import org.bouncycastle.crypto.params.ParametersWithRandom; -import org.bouncycastle.crypto.params.RSAKeyParameters; -import org.bouncycastle.crypto.params.SRP6GroupParameters; +import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.prng.DigestRandomGenerator; import org.bouncycastle.tls.AlertDescription; import org.bouncycastle.tls.EncryptionAlgorithm; @@ -67,14 +56,7 @@ import org.bouncycastle.tls.crypto.TlsSRP6VerifierGenerator; import org.bouncycastle.tls.crypto.TlsSRPConfig; import org.bouncycastle.tls.crypto.TlsSecret; -import org.bouncycastle.tls.crypto.impl.AbstractTlsCrypto; -import org.bouncycastle.tls.crypto.impl.TlsAEADCipher; -import org.bouncycastle.tls.crypto.impl.TlsAEADCipherImpl; -import org.bouncycastle.tls.crypto.impl.TlsBlockCipher; -import org.bouncycastle.tls.crypto.impl.TlsBlockCipherImpl; -import org.bouncycastle.tls.crypto.impl.TlsEncryptor; -import org.bouncycastle.tls.crypto.impl.TlsImplUtils; -import org.bouncycastle.tls.crypto.impl.TlsNullCipher; +import org.bouncycastle.tls.crypto.impl.*; import org.bouncycastle.util.Arrays; /** @@ -162,6 +144,9 @@ public TlsCipher createCipher(TlsCryptoParameters cryptoParams, int encryptionAl case EncryptionAlgorithm.CHACHA20_POLY1305: // NOTE: Ignores macAlgorithm return createChaCha20Poly1305(cryptoParams); + case EncryptionAlgorithm.SM4_CBC: + // Chinese GMSSL SM4 mode + return createSM4Cipher(cryptoParams, macAlgorithm); case EncryptionAlgorithm.NULL: return createNullCipher(cryptoParams, macAlgorithm); case EncryptionAlgorithm.SEED_CBC: @@ -195,28 +180,25 @@ protected TlsEncryptor createEncryptor(TlsCertificate certificate) BcTlsCertificate bcCert = BcTlsCertificate.convert(this, certificate); bcCert.validateKeyUsage(KeyUsage.keyEncipherment); - final RSAKeyParameters pubKeyRSA = bcCert.getPubKeyRSA(); - return new TlsEncryptor() + final AsymmetricKeyParameter publicKey = bcCert.getPublicKey(); + + if(publicKey instanceof RSAKeyParameters) { - public byte[] encrypt(byte[] input, int inOff, int length) - throws IOException - { - try - { - PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine()); - encoding.init(true, new ParametersWithRandom(pubKeyRSA, getSecureRandom())); - return encoding.processBlock(input, inOff, length); - } - catch (InvalidCipherTextException e) - { - /* - * This should never happen, only during decryption. - */ - throw new TlsFatalAlert(AlertDescription.internal_error, e); - } - } - }; + final RSAKeyParameters pubKeyRSA = (RSAKeyParameters) publicKey; + return new BcTlsRSAEncryptor(pubKeyRSA, getSecureRandom()); + } + else if(publicKey instanceof ECPublicKeyParameters) + { + final ECPublicKeyParameters pubKeySM2 = (ECPublicKeyParameters) publicKey; + return new BcGmsslEncryptor(pubKeySM2, getSecureRandom()); + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + } public TlsNonceGenerator createNonceGenerator(byte[] additionalSeedMaterial) @@ -376,6 +358,8 @@ public Digest createDigest(short hashAlgorithm) return new SHA384Digest(); case HashAlgorithm.sha512: return new SHA512Digest(); + case HashAlgorithm.sm3: + return new SM3Digest(); default: throw new IllegalArgumentException("invalid HashAlgorithm: " + HashAlgorithm.getText(hashAlgorithm)); } @@ -437,11 +421,22 @@ public static Digest cloneDigest(short hashAlgorithm, Digest hash) return new SHA384Digest((SHA384Digest)hash); case HashAlgorithm.sha512: return new SHA512Digest((SHA512Digest)hash); + case HashAlgorithm.sm3: + return new SM3Digest((SM3Digest)hash); default: throw new IllegalArgumentException("invalid HashAlgorithm: " + HashAlgorithm.getText(hashAlgorithm)); } } + protected TlsCipher createSM4Cipher(TlsCryptoParameters cryptoParams, int macAlgorithm) + throws IOException + { + // SM4 Block size 128bit => 16 byte + return new TlsBlockCipher(this, cryptoParams, new BlockOperator(createSM4BlockCipher(), true), + new BlockOperator(createSM4BlockCipher(), false), createMAC(cryptoParams, macAlgorithm), + createMAC(cryptoParams, macAlgorithm), 16); + } + protected TlsCipher createAESCipher(TlsCryptoParameters cryptoParams, int cipherKeySize, int macAlgorithm) throws IOException { @@ -524,6 +519,11 @@ protected TlsBlockCipher createSEEDCipher(TlsCryptoParameters cryptoParams, int createMAC(cryptoParams, macAlgorithm), 16); } + protected BlockCipher createSM4Engine() + { + return new SM4Engine(); + } + protected BlockCipher createAESEngine() { return new AESEngine(); @@ -544,6 +544,11 @@ protected BlockCipher createAESBlockCipher() return new CBCBlockCipher(createAESEngine()); } + protected BlockCipher createSM4BlockCipher() + { + return new CBCBlockCipher(createSM4Engine()); + } + protected BlockCipher createARIABlockCipher() { return new CBCBlockCipher(createARIAEngine()); diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsSM2Verifier.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsSM2Verifier.java new file mode 100644 index 0000000000..2de5291c13 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/BcTlsSM2Verifier.java @@ -0,0 +1,37 @@ +package org.bouncycastle.tls.crypto.impl.bc; + +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.tls.DigitallySigned; + +/** + * SM2 signature verifier + * + * force use SM2WithSM3 signature verify data. + * + * + */ +public class BcTlsSM2Verifier extends BcTlsVerifier +{ + protected BcTlsSM2Verifier(BcTlsCrypto crypto, ECPublicKeyParameters publicKey) + { + super(crypto, publicKey); + } + + /** + * verify signature + * + * @param signedParams signature + * @param hash raw message + * @return true/false (pass or not) + */ + public boolean verifyRawSignature(DigitallySigned signedParams, byte[] hash) + { + // ignore SignatureAndHashAlgorithm force use SM2WithSM3 signature alg. + Signer signer = new SM2Signer(); + signer.init(false, publicKey); + signer.update(hash, 0, hash.length); + return signer.verifySignature(signedParams.getSignature()); + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/SM2Cipher.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/SM2Cipher.java new file mode 100644 index 0000000000..074d2cf0ba --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/bc/SM2Cipher.java @@ -0,0 +1,180 @@ +package org.bouncycastle.tls.crypto.impl.bc; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.util.BigIntegers; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Enumeration; + +/** + * GMT 0009-2012 + *

+ * sm2 encrypted data specific struct + * + * + * @since 2021-03-10 13:28:12 + */ +public class SM2Cipher extends ASN1Object +{ + /* + * SM2Cipher ::== SEQUENCE{ + * XCoordinate INTEGER, --X Portion + * YCoordinate INTEGER, --Y Portion + * HASH OCTET STRING SIZE(32), --Plaintext sm3 hash + * CipherText OCTET STRING --CipherText + * } + */ + + private ASN1Integer xCoordinate; + private ASN1Integer yCoordinate; + private ASN1OctetString hash; + private ASN1OctetString cipherText; + + public SM2Cipher() + { + super(); + } + + public SM2Cipher(ASN1Sequence seq) + { + Enumeration e = seq.getObjects(); + xCoordinate = ASN1Integer.getInstance(e.nextElement()); + yCoordinate = ASN1Integer.getInstance(e.nextElement()); + hash = ASN1OctetString.getInstance(e.nextElement()); + cipherText = ASN1OctetString.getInstance(e.nextElement()); + } + + public static SM2Cipher getInstance(Object o) + { + if(o instanceof SM2Cipher) + { + return (SM2Cipher) o; + } + else if(o != null) + { + return new SM2Cipher(ASN1Sequence.getInstance(o)); + } + return null; + } + + public ASN1Integer getxCoordinate() + { + return xCoordinate; + } + + public void setxCoordinate(ASN1Integer xCoordinate) + { + this.xCoordinate = xCoordinate; + } + + public ASN1Integer getyCoordinate() + { + return yCoordinate; + } + + public void setyCoordinate(ASN1Integer yCoordinate) + { + this.yCoordinate = yCoordinate; + } + + public ASN1OctetString getHash() + { + return hash; + } + + public void setHash(ASN1OctetString hash) + { + this.hash = hash; + } + + public ASN1OctetString getCipherText() + { + return cipherText; + } + + public void setCipherText(ASN1OctetString cipherText) + { + this.cipherText = cipherText; + } + + public ASN1Primitive toASN1Primitive() + { + ASN1EncodableVector v = new ASN1EncodableVector(4); + v.add(xCoordinate); + v.add(yCoordinate); + v.add(hash); + v.add(cipherText); + return new DERSequence(v); + } + + /** + * Convert ASN.1 Struct to C1C3C2 format + * + * @return C1C3C2 + * @throws IOException + */ + public byte[] convertC1C3C2() throws IOException + { + /* + * construct GMT0009-2012 encrypted data struct + */ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + + final byte[] x = new byte[32]; + final byte[] y = new byte[32]; + + byte[] tmp = BigIntegers.asUnsignedByteArray(getxCoordinate().getValue()); + System.arraycopy(tmp, 0, x, 32 - tmp.length, tmp.length); + tmp = BigIntegers.asUnsignedByteArray(getyCoordinate().getValue()); + System.arraycopy(tmp, 0, y, 32 - tmp.length, tmp.length); + + // C1 + // read 1 byte for uncompressed point prefix 0x04 + stream.write(0x04); + stream.write(x); + stream.write(y); + // C3 + stream.write(getHash().getOctets()); + // C2 + stream.write(getCipherText().getOctets()); + stream.flush(); + return stream.toByteArray(); + } + + /** + * Convert SM2 encrypted result format of c1c3c2 to ASN.1 SM2Cipher + * + * @param c1c3c2 encrypted result + * @return SM2Cipher + * @throws IOException + */ + static public SM2Cipher fromC1C3C2(byte[] c1c3c2) throws IOException + { + /* + * construct GMT0009-2012 encrypted data struct + */ + ByteArrayInputStream stream = new ByteArrayInputStream(c1c3c2); + // read 1 byte for uncompressed point prefix 0x04 + stream.read(); + final byte[] x = new byte[32]; + final byte[] y = new byte[32]; + final byte[] hash = new byte[32]; + int length = c1c3c2.length - 1 - 32 - 32 - 32; + final byte[] cipherText = new byte[length]; + stream.read(x); + stream.read(y); + stream.read(hash); + stream.read(cipherText); + + final SM2Cipher sm2Cipher = new SM2Cipher(); + sm2Cipher.setxCoordinate(new ASN1Integer(new BigInteger(1, x))); + sm2Cipher.setyCoordinate(new ASN1Integer(new BigInteger(1, y))); + sm2Cipher.setHash(new DEROctetString(hash)); + sm2Cipher.setCipherText(new DEROctetString(cipherText)); + return sm2Cipher; + } +} diff --git a/tls/src/test/java/org/bouncycastle/tls/test/GMSSLClientTest.java b/tls/src/test/java/org/bouncycastle/tls/test/GMSSLClientTest.java new file mode 100644 index 0000000000..f8cab2696b --- /dev/null +++ b/tls/src/test/java/org/bouncycastle/tls/test/GMSSLClientTest.java @@ -0,0 +1,126 @@ +package org.bouncycastle.tls.test; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.bouncycastle.jsse.provider.gm.GMSimpleSSLClient; +import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory; +import org.bouncycastle.tls.TlsClientProtocol; +import org.bouncycastle.util.io.Streams; + +import javax.net.ssl.*; +import java.io.IOException; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.URL; +import java.security.*; + +/** + * Test GMSSL's client connection status + * + * + * @since 2021-03-05 11:31:29 + */ +public class GMSSLClientTest +{ + + public static void main(String[] args) + throws Exception { + final BouncyCastleProvider provider = new BouncyCastleProvider(); + Security.addProvider(provider); + Security.addProvider(new BouncyCastleJsseProvider()); + +// String host = "localhost"; +// int port = 5557; +// String host = "sm2test.ovssl.cn"; +// int port = 443; +// bc(host, port); +// jsse(host, port); + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() + { + public boolean verify(String s, SSLSession sslSession) + { + return true; + } + }); + httpGet("https://sm2test.ovssl.cn/"); + } + + private static void bc(String host, int port) throws IOException + { + final GMSimpleSSLClient client = new GMSimpleSSLClient(); + Socket s = new Socket(host, port); + TlsClientProtocol protocol = new TlsClientProtocol(s.getInputStream(), s.getOutputStream()); + protocol.connect(client); + + + OutputStream out = protocol.getOutputStream(); + String req = "GET / HTTP/1.1\r\n" + + "Host: "+ host + "\r\n" + + "Connection: close\r\n\r\n"; + + out.write(req.getBytes("UTF-8")); + out.flush(); + Streams.pipeAll(protocol.getInputStream(), System.out); + out.close(); + } + + private static void jsse(String host, int port) throws IOException, NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException + { + SSLContext clientContext = SSLContext.getInstance("TLS", BouncyCastleJsseProvider.PROVIDER_NAME); + clientContext.init(new KeyManager[]{}, new TrustManager[]{}, new SecureRandom()); + SSLSocketFactory fact = clientContext.getSocketFactory(); + SSLSocket cSock = (SSLSocket) fact.createSocket(host, port); + + OutputStream out = cSock.getOutputStream(); + String req = "GET / HTTP/1.1\r\n" + + "Host: "+host+"\r\n" + + "Connection: close\r\n\r\n"; + + out.write(req.getBytes("UTF-8")); + out.flush(); + InputStream in = cSock.getInputStream(); + byte[] buffer = new byte[2048]; + in.read(buffer); + System.out.println(new String(buffer)); + + out.close(); + in.close(); + } + + + private static void httpGet(String urlStr) + { + + HttpsURLConnection connection = null; + + try + { + URL url = new URL(urlStr); + connection = (HttpsURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setSSLSocketFactory(new GMSimpleSSLSocketFactory()); + final InputStream input = connection.getInputStream(); + byte[] buff = new byte[4096]; + + int n; + while((n=input.read(buff)) != -1) + { + System.out.write(buff, 0, n); + } + input.close(); + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + if (connection != null) + { + connection.disconnect(); + } + } + } +} diff --git a/tls/src/test/java/org/bouncycastle/tls/test/GMSSLServerTest.java b/tls/src/test/java/org/bouncycastle/tls/test/GMSSLServerTest.java new file mode 100644 index 0000000000..c42b2b5821 --- /dev/null +++ b/tls/src/test/java/org/bouncycastle/tls/test/GMSSLServerTest.java @@ -0,0 +1,116 @@ +package org.bouncycastle.tls.test; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.jsse.provider.gm.GMSimpleSSLServer; +import org.bouncycastle.tls.*; +import org.bouncycastle.tls.crypto.TlsCertificate; +import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto; + +import java.io.*; +import java.net.*; +import java.security.SecureRandom; + +/** + * A simple test designed to conduct a GMSSL handshake with an external GMSSL client. + * + * + * @since 2021-03-16 09:57:58 + */ +public class GMSSLServerTest +{ + static BcTlsCrypto crypto; + static AsymmetricKeyParameter signKey; + static AsymmetricKeyParameter encKey; + static Certificate certList; + + public static void main(String[] args) throws Exception + { + + int port = 5559; + ServerSocket ss = new ServerSocket(port, 16); + + crypto = new BcTlsCrypto(new SecureRandom()); + + certList = new Certificate(new TlsCertificate[]{ + TlsTestUtils.loadCertificateResource(crypto, "x509-server-sm2-sign.pem"), + TlsTestUtils.loadCertificateResource(crypto, "x509-server-sm2-enc.pem"), + }); + + signKey = TlsTestUtils.loadBcPrivateKeyResource("x509-server-key-sm2-sign.pem"); + encKey = TlsTestUtils.loadBcPrivateKeyResource("x509-server-key-sm2-enc.pem"); + + try + { + while (true) + { + Socket s = ss.accept(); + System.out.println("--------------------------------------------------------------------------------"); + System.out.println("Accepted " + s); + ServerThread t = new ServerThread(s); + t.start(); + } + } finally + { + ss.close(); + } + } + + static class ServerThread extends Thread + { + private final Socket s; + + ServerThread(Socket s) + { + this.s = s; + } + + public void run() + { + try + { + GMSimpleSSLServer server = new GMSimpleSSLServer(crypto, certList, signKey, encKey); + TlsServerProtocol serverProtocol = new TlsServerProtocol(s.getInputStream(), s.getOutputStream()); + serverProtocol.accept(server); + + final InputStream in = serverProtocol.getInputStream(); + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + System.out.println(">> Request:\n"); + String lineContent = null; + while ((lineContent = br.readLine()) != null) + { + if(lineContent.length() == 0) + { + break; + } + System.out.println("> " + lineContent); + } + System.out.println(); + + OutputStream outputStream = serverProtocol.getOutputStream(); + String resp = "HTTP/1.1 200 OK\r\n" + "Content-Length: 6\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n" + "hello\n"; + outputStream.write(resp.getBytes("UTF-8")); + outputStream.flush(); + serverProtocol.close(); + System.out.println(">> Responded"); + } catch (Exception e) + { + if(e instanceof EOFException) + { + return; + } + throw new RuntimeException(e); + } finally + { + try + { + s.close(); + } catch (IOException e) + { + } finally + { + } + } + } + } +} diff --git a/tls/src/test/java/org/bouncycastle/tls/test/GMSimpleSSLSocketFactoryTest.java b/tls/src/test/java/org/bouncycastle/tls/test/GMSimpleSSLSocketFactoryTest.java new file mode 100644 index 0000000000..4e552f352a --- /dev/null +++ b/tls/src/test/java/org/bouncycastle/tls/test/GMSimpleSSLSocketFactoryTest.java @@ -0,0 +1,169 @@ +package org.bouncycastle.tls.test; + + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory; +import org.bouncycastle.tls.Certificate; +import org.bouncycastle.tls.crypto.TlsCertificate; +import org.bouncycastle.tls.crypto.impl.bc.BcTlsCrypto; +import org.bouncycastle.util.io.Streams; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.SecureRandom; + +public class GMSimpleSSLSocketFactoryTest +{ + static final int port = 5558; + + public static void main(String[] args) throws IOException, InterruptedException + { + + BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom()); + final Certificate certList = new Certificate(new TlsCertificate[]{ + TlsTestUtils.loadCertificateResource(crypto, "x509-server-sm2-sign.pem"), + TlsTestUtils.loadCertificateResource(crypto, "x509-server-sm2-enc.pem"), + }); + final AsymmetricKeyParameter signKey = TlsTestUtils.loadBcPrivateKeyResource("x509-server-key-sm2-sign.pem"); + final AsymmetricKeyParameter encKey = TlsTestUtils.loadBcPrivateKeyResource("x509-server-key-sm2-enc.pem"); + + + bootServer(port, certList, signKey, encKey); +// bootClient("sm2test.ovssl.cn", 443); + + } + + /* + // GMSSL HttpClient Example + import org.apache.http.HttpResponse; + import org.apache.http.client.HttpClient; + import org.apache.http.client.methods.HttpGet; + import org.apache.http.conn.ssl.NoopHostnameVerifier; + import org.apache.http.conn.ssl.SSLConnectionSocketFactory; + import org.apache.http.impl.client.HttpClientBuilder; + import org.bouncycastle.jce.provider.BouncyCastleProvider; + import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; + import org.bouncycastle.jsse.provider.gm.GMSimpleSSLSocketFactory; + + import java.security.Security; + public class GMHttpClient { + public static void main(String[] args) throws Exception { + Security.addProvider(new BouncyCastleProvider()); + Security.addProvider(new BouncyCastleJsseProvider()); + + GMSimpleSSLSocketFactory factory = new GMSimpleSSLSocketFactory(); + + SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(factory, new NoopHostnameVerifier()); + HttpClient client = HttpClientBuilder.create() + .setSSLSocketFactory(sf) + .build(); + + final HttpResponse response = client.execute(new HttpGet("https://127.0.0.1:5558")); + response.getEntity().writeTo(System.out); + } + } + */ + + private static void bootServer(int port, Certificate certList, AsymmetricKeyParameter signKey, AsymmetricKeyParameter encKey) + { + ServerSocket ss = null; + try + { + final GMSimpleSSLSocketFactory socketFactory = GMSimpleSSLSocketFactory.ServerFactory(certList, signKey, encKey); + ss = new ServerSocket(port, 16); + + while (true) + { + Socket s = ss.accept(); + System.out.println("--------------------------------------------------------------------------------"); + System.out.println("Accepted " + s); + s = socketFactory.createSocket(s, "", 0, true); + new Thread(new ServerThread(s)).start(); + } + } catch (IOException e) + { + e.printStackTrace(); + } finally + { + try + { + if(ss != null) + { + ss.close(); + } + } catch (IOException e) + { + } + } + } + + private static void bootClient(String host, int port) throws IOException + { + final GMSimpleSSLSocketFactory socketFactory = GMSimpleSSLSocketFactory.ClientFactory(); + final Socket cSock = socketFactory.createSocket(host, port); + OutputStream out = cSock.getOutputStream(); + String req = "GET / HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; + + out.write(req.getBytes("UTF-8")); + out.flush(); + InputStream in = cSock.getInputStream(); + Streams.pipeAll(in, System.out); + out.close(); + in.close(); + } + + static class ServerThread implements Runnable + { + private final Socket socket; + + public ServerThread(Socket s) + { + this.socket = s; + } + + public void run() + { + try + { + InputStreamReader isr = new InputStreamReader(socket.getInputStream()); + BufferedReader br = new BufferedReader(isr); + System.out.println(">> Request:\n"); + String lineContent = null; + while ((lineContent = br.readLine()) != null) + { + if(lineContent.length() == 0) + { + break; + } + System.out.println("> " + lineContent); + } + System.out.println(); + + OutputStream outputStream = socket.getOutputStream(); + String resp = "HTTP/1.1 200 OK\r\n" + "Content-Length: 6\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "\r\n" + "hello\n"; + outputStream.write(resp.getBytes("UTF-8")); + outputStream.flush(); + socket.close(); + System.out.println(">> Responded"); + } catch (IOException e) + { + if(e instanceof EOFException) + { + return; + } + e.printStackTrace(); + } finally + { + try + { + socket.close(); + } catch (IOException e) + { + + } + } + } + } + +} \ No newline at end of file diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-key-sm2.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-key-sm2.pem new file mode 100644 index 0000000000..d1433c53f3 --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-key-sm2.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgPja9Wd7sQOI2ZEis +h1FZbHcsO4NUxqT9lSDiI9pQOBmgCgYIKoEcz1UBgi2hRANCAARnMDPZoNh72vvL +bO+8W5QjkV5wcOZormBLJf+ZCAi59jqJApvUUtmz9m/ALJMeIVur0oVUsvlI/hKz +vUvnv3XK +-----END PRIVATE KEY----- diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-sm2.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-sm2.pem new file mode 100644 index 0000000000..f8c16e8591 --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-ca-sm2.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB5DCCAYmgAwIBAgIIAs4Dt8fd5ncwCgYIKoEcz1UBg3UwRTELMAkGA1UEBhMC +Q04xETAPBgNVBAgTCFpoZWppYW5nMREwDwYDVQQHEwhIYW5nemhvdTEQMA4GA1UE +ChMHVGVzdCBDQTAeFw0yMTAzMTIwMzQ1MzVaFw0zMTAzMTIwMzQ1MzVaMEUxCzAJ +BgNVBAYTAkNOMREwDwYDVQQIEwhaaGVqaWFuZzERMA8GA1UEBxMISGFuZ3pob3Ux +EDAOBgNVBAoTB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAARnMDPZ +oNh72vvLbO+8W5QjkV5wcOZormBLJf+ZCAi59jqJApvUUtmz9m/ALJMeIVur0oVU +svlI/hKzvUvnv3XKo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUJzDbP6IFeVDlegj4rMuWgPNPw8MwHwYDVR0jBBgwFoAUJzDb +P6IFeVDlegj4rMuWgPNPw8MwCgYIKoEcz1UBg3UDSQAwRgIhAIk4RUSafZ/Mx6Fi +vD0dzrehJyZ3NdTgfkpaGBtyU+xCAiEA5Pv54SXiCAH5RwYQbaCxCtml8NTn3WVd +DVM1URKXiNQ= +-----END CERTIFICATE----- diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-enc.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-enc.pem new file mode 100644 index 0000000000..c432275800 --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-enc.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgOwrsOroddxkoOYoE +xZlbDdPsrSY6ug08Saf/IkMSbxegCgYIKoEcz1UBgi2hRANCAARB5CzonabehIxs +RegFs1Vs3IZrXJXzZIPH2F1afv/NLj3OSngVUMbGK0sTOSfdn1wLKiLAvvzYuHgb +sA3LAW/E +-----END PRIVATE KEY----- diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-sign.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-sign.pem new file mode 100644 index 0000000000..3619dbfba6 --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-key-sm2-sign.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgsanX/ZPknQYj5Ihn +J394MHsOBFB7GI2QuqsOWm4B+kGgCgYIKoEcz1UBgi2hRANCAATdyAq+Ik8vkBPp +CyMn7Q+uRiipEvNWVNm+3+q5zM4oisxMuneyCt51HlnA2Iom9T7tL25ejnMYt0UB +L4Aj1aaU +-----END PRIVATE KEY----- diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-enc.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-enc.pem new file mode 100644 index 0000000000..5751eb0d0b --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-enc.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7TCCAZOgAwIBAgIIAs4Dt8ftfmcwCgYIKoEcz1UBg3UwRTELMAkGA1UEBhMC +Q04xETAPBgNVBAgTCFpoZWppYW5nMREwDwYDVQQHEwhIYW5nemhvdTEQMA4GA1UE +ChMHVGVzdCBDQTAeFw0yMTAzMTIwMzQ2MzdaFw0zMTAzMTIwMzQ1MzVaMGAxCzAJ +BgNVBAYTAkNOMREwDwYDVQQIEwhaaGVqaWFuZzERMA8GA1UEBxMISGFuZ3pob3Ux +ETAPBgNVBAoTCHRlc3Qgb3JnMRgwFgYDVQQDDA9URVNUX1RMU19TRVJWRVIwWTAT +BgcqhkjOPQIBBggqgRzPVQGCLQNCAARB5CzonabehIxsRegFs1Vs3IZrXJXzZIPH +2F1afv/NLj3OSngVUMbGK0sTOSfdn1wLKiLAvvzYuHgbsA3LAW/Eo1IwUDAOBgNV +HQ8BAf8EBAMCBDAwHQYDVR0OBBYEFEmiOBKBDtDro5jJ45jyKG2NNjhFMB8GA1Ud +IwQYMBaAFCcw2z+iBXlQ5XoI+KzLloDzT8PDMAoGCCqBHM9VAYN1A0gAMEUCIQDd +XGPbrR1DZkqeXzkqAIlsLzXfS61EqW9mH/xkhD0UvgIgBKg3S1uRS4YL25ZC472v +Dl6tCE72cMM6xnBzSwQl6oY= +-----END CERTIFICATE----- diff --git a/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-sign.pem b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-sign.pem new file mode 100644 index 0000000000..24148aad6e --- /dev/null +++ b/tls/src/test/resources/org/bouncycastle/tls/test/x509-server-sm2-sign.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICDTCCAbKgAwIBAgIIAs4Dt8ftdtcwCgYIKoEcz1UBg3UwRTELMAkGA1UEBhMC +Q04xETAPBgNVBAgTCFpoZWppYW5nMREwDwYDVQQHEwhIYW5nemhvdTEQMA4GA1UE +ChMHVGVzdCBDQTAeFw0yMTAzMTIwMzQ2MzdaFw0zMTAzMTIwMzQ1MzVaMGAxCzAJ +BgNVBAYTAkNOMREwDwYDVQQIEwhaaGVqaWFuZzERMA8GA1UEBxMISGFuZ3pob3Ux +ETAPBgNVBAoTCHRlc3Qgb3JnMRgwFgYDVQQDDA9URVNUX1RMU19TRVJWRVIwWTAT +BgcqhkjOPQIBBggqgRzPVQGCLQNCAATdyAq+Ik8vkBPpCyMn7Q+uRiipEvNWVNm+ +3+q5zM4oisxMuneyCt51HlnA2Iom9T7tL25ejnMYt0UBL4Aj1aaUo3EwbzAOBgNV +HQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1Ud +DgQWBBSGSgh9dcFdcLn3NjQBJpYachVZYzAfBgNVHSMEGDAWgBQnMNs/ogV5UOV6 +CPisy5aA80/DwzAKBggqgRzPVQGDdQNJADBGAiEA6z2Jjhiok+e+Y6tEJlnn3dcE +kJX+cIBiHkDnjtQOIi0CIQD0nEDlrbC5PyZY9Ydk/dLfmpmjsknNmx3GJgG9MA/z +VA== +-----END CERTIFICATE-----