diff --git a/pgjdbc/pom.xml b/pgjdbc/pom.xml index afaf1b9063f1042f85fa36367bcbe0324973f380..d13e1c1d340bec95f94f55eb01b26ed21c19d33a 100644 --- a/pgjdbc/pom.xml +++ b/pgjdbc/pom.xml @@ -30,6 +30,11 @@ + + org.bouncycastle + bcprov-jdk15on + 1.56 + org.slf4j slf4j-api diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java index 4c77349bd513cbe3f15f831d490bcab355a3678d..2fbd73b4a36b604af0cfc14d71d5be00d107bc20 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java +++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java @@ -65,9 +65,11 @@ public class ConnectionFactoryImpl extends ConnectionFactory { public static String USE_BOOLEAN = "false"; private static final int AUTH_REQ_SHA256 = 10; private static final int AUTH_REQ_MD5_SHA256 = 11; + private static final int AUTH_REQ_SM3 = 13; private static final int PLAIN_PASSWORD = 0; private static final int MD5_PASSWORD = 1; private static final int SHA256_PASSWORD = 2; + private static final int SM3_PASSWORD = 6; private static final int ERROR_PASSWORD = 3; private static final int PROTOCOL_VERSION_351 = 351; private static final int PROTOCOL_VERSION_350 = 350; @@ -575,6 +577,40 @@ public class ConnectionFactoryImpl extends ConnectionFactory { break; } + case AUTH_REQ_SM3: { + LOGGER.trace("[" + connectInfo + "] " + "AUTH_REQ_SM3"); + int passwordStoredMethod = pgStream.receiveInteger4(); + if (password == null) + throw new PSQLException( + GT.tr( + "The server requested password-based authentication, but no password" + + " was provided."), + PSQLState.CONNECTION_REJECTED); + if (passwordStoredMethod == SM3_PASSWORD) { + String random64code = pgStream.receiveString(64); + String token = pgStream.receiveString(8); + int server_iteration = pgStream.receiveInteger4(); + byte[] result = null; + MD5Digest.setIsSha256(false); + result = MD5Digest.RFC5802Algorithm(password, random64code, token, server_iteration); + if (result == null) + throw new PSQLException( + GT.tr("Invalid username/password,login denied."), + PSQLState.CONNECTION_REJECTED); + pgStream.sendChar('p'); + pgStream.sendInteger4(4 + result.length + 1); + pgStream.send(result); + pgStream.sendChar(0); + pgStream.flush(); + break; + } else { + throw new PSQLException( + GT.tr( + "The password-stored method is not supported, must be plain, md5, " + + "sha256 or sm3."), + PSQLState.CONNECTION_REJECTED); + } + } case AUTH_REQ_SHA256: { LOGGER.trace("[" + connectInfo + "] " + "AUTH_REQ_SHA256"); byte[] digest; diff --git a/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java b/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java index 4178c2a3bce3cfef7a5b1ba7110862bc5990c3dd..b322b04aae2a51a06d3165488d3508c248f1dea2 100644 --- a/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java +++ b/pgjdbc/src/main/java/org/postgresql/util/MD5Digest.java @@ -9,7 +9,10 @@ import org.postgresql.log.Log; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.spec.InvalidKeySpecException; import java.util.Locale; @@ -19,6 +22,8 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.PBEKeySpec; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; /** * MD5-based utility function to obfuscate passwords before network transmission. @@ -26,6 +31,18 @@ import javax.crypto.spec.PBEKeySpec; * @author Jeremy Wohl */ public class MD5Digest { + private static final Provider provider = new BouncyCastleProvider(); + + private static boolean isSha256 = true; + + public static boolean getIsSha256 () { + return isSha256; + } + + public static void setIsSha256 (boolean val) { + isSha256 = val; + } + private static Log LOGGER = Logger.getLogger(MD5Digest.class.getName()); private MD5Digest() { @@ -133,6 +150,20 @@ public class MD5Digest { return md.digest(); } + private static byte[] sm3(byte[] str) { + Security.addProvider(provider); + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SM3", PROVIDER_NAME); + } catch (GeneralSecurityException e) { + LOGGER.info("Sm3 encode failed.", e); + } + if (md == null) { + return new byte[0]; + } + return md.digest(str); + } + private static String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder(""); if (src == null || src.length <= 0) { @@ -267,7 +298,12 @@ public class MD5Digest { byte[] K = generateKFromPBKDF2(password, random64code, server_iteration); byte[] server_key = getKeyFromHmac(K, "Sever Key".getBytes("UTF-8")); byte[] client_key = getKeyFromHmac(K, "Client Key".getBytes("UTF-8")); - byte[] stored_key = sha256(client_key); + byte[] stored_key = null; + if (getIsSha256()) { + stored_key = sha256(client_key); + } else { + stored_key = sm3(client_key); + } byte[] tokenbyte = hexStringToBytes(token); byte[] client_signature = getKeyFromHmac(server_key, tokenbyte); if (server_signature != null && !server_signature.equals(bytesToHexString(client_signature))) return new byte[0];