Skip to content

Commit

Permalink
WL#11200, Add caching_sha2_password support.
Browse files Browse the repository at this point in the history
  • Loading branch information
soklakov committed Feb 1, 2018
1 parent 2c80333 commit 500199b
Show file tree
Hide file tree
Showing 8 changed files with 751 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Version 5.1.46

- WL#11200, Add caching_sha2_password support.

- Fix for Bug#88227 (27029657), Connector/J 5.1.44 cannot be used against MySQL 5.7.20 without warnings.

- Fix for Bug#27374581, CONNECTION FAILS WHEN GPL SERVER STARTED WITH TLS-VERSION=TLSV1.2.
Expand Down
7 changes: 5 additions & 2 deletions src/com/mysql/jdbc/ExportControlled.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,14 +507,17 @@ public static RSAPublicKey decodeRSAPublicKey(String key, ExceptionInterceptor i
}
}

public static byte[] encryptWithRSAPublicKey(byte[] source, RSAPublicKey key, ExceptionInterceptor interceptor) throws SQLException {
public static byte[] encryptWithRSAPublicKey(byte[] source, RSAPublicKey key, String transformation, ExceptionInterceptor interceptor) throws SQLException {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(source);
} catch (Exception ex) {
throw SQLError.createSQLException(ex.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, ex, interceptor);
}
}

public static byte[] encryptWithRSAPublicKey(byte[] source, RSAPublicKey key, ExceptionInterceptor interceptor) throws SQLException {
return encryptWithRSAPublicKey(source, key, "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", interceptor);
}
}
7 changes: 7 additions & 0 deletions src/com/mysql/jdbc/MysqlIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.Properties;
import java.util.zip.Deflater;

import com.mysql.jdbc.authentication.CachingSha2PasswordPlugin;
import com.mysql.jdbc.authentication.MysqlClearPasswordPlugin;
import com.mysql.jdbc.authentication.MysqlNativePasswordPlugin;
import com.mysql.jdbc.authentication.MysqlOldPasswordPlugin;
Expand Down Expand Up @@ -1465,6 +1466,12 @@ private void loadAuthenticationPlugins() throws SQLException {
defaultIsFound = true;
}

plugin = new CachingSha2PasswordPlugin();
plugin.init(this.connection, this.connection.getProperties());
if (addAuthenticationPlugin(plugin)) {
defaultIsFound = true;
}

// plugins from authenticationPluginClasses connection parameter
String authenticationPluginClasses = this.connection.getAuthenticationPlugins();
if (authenticationPluginClasses != null && !"".equals(authenticationPluginClasses)) {
Expand Down
60 changes: 57 additions & 3 deletions src/com/mysql/jdbc/Security.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
Expand All @@ -24,6 +24,7 @@
package com.mysql.jdbc;

import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

Expand All @@ -35,6 +36,8 @@ public class Security {

private static final int SHA1_HASH_SIZE = 20;

private static int CACHING_SHA2_DIGEST_LENGTH = 32;

/**
* Returns hex value for given char
*/
Expand All @@ -44,7 +47,7 @@ private static int charVal(char c) {

/*
* Convert password in salted form to binary string password and hash-salt
* For old password this involes one more hashing
* For old password this involves one more hashing
*
* SYNOPSIS get_hash_and_password() salt IN Salt to convert from pversion IN
* Password version to use hash OUT Store zero ended hash here bin_password
Expand Down Expand Up @@ -208,7 +211,7 @@ static String makeScrambledPassword(String password) throws NoSuchAlgorithmExcep
/**
* Encrypt/Decrypt function used for password encryption in authentication
*
* Simple XOR is used here but it is OK as we crypt random strings
* Simple XOR is used here but it is OK as we encrypt random strings
*
* @param from
* IN Data for encryption
Expand Down Expand Up @@ -325,6 +328,57 @@ public static byte[] scramble411(String password, String seed, String passwordEn
return toBeXord;
}

/**
* Scrambling for caching_sha2_password plugin.
*
* <pre>
* Scramble = XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce))
* </pre>
*
* @throws DigestException
*/
public static byte[] scrambleCachingSha2(byte[] password, byte[] seed) throws DigestException {
/*
* Server does it in 4 steps (see sql/auth/sha2_password_common.cc Generate_scramble::scramble method):
*
* SHA2(src) => digest_stage1
* SHA2(digest_stage1) => digest_stage2
* SHA2(digest_stage2, m_rnd) => scramble_stage1
* XOR(digest_stage1, scramble_stage1) => scramble
*/
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ex) {
throw new AssertionFailedException(ex);
}

byte[] dig1 = new byte[CACHING_SHA2_DIGEST_LENGTH];
byte[] dig2 = new byte[CACHING_SHA2_DIGEST_LENGTH];
byte[] scramble1 = new byte[CACHING_SHA2_DIGEST_LENGTH];

// SHA2(src) => digest_stage1
md.update(password, 0, password.length);
md.digest(dig1, 0, CACHING_SHA2_DIGEST_LENGTH);
md.reset();

// SHA2(digest_stage1) => digest_stage2
md.update(dig1, 0, dig1.length);
md.digest(dig2, 0, CACHING_SHA2_DIGEST_LENGTH);
md.reset();

// SHA2(digest_stage2, m_rnd) => scramble_stage1
md.update(dig2, 0, dig1.length);
md.update(seed, 0, seed.length);
md.digest(scramble1, 0, CACHING_SHA2_DIGEST_LENGTH);

// XOR(digest_stage1, scramble_stage1) => scramble
byte[] mysqlScrambleBuff = new byte[CACHING_SHA2_DIGEST_LENGTH];
xorString(dig1, mysqlScrambleBuff, scramble1, CACHING_SHA2_DIGEST_LENGTH);

return mysqlScrambleBuff;
}

/**
* Prevent construction.
*/
Expand Down
175 changes: 175 additions & 0 deletions src/com/mysql/jdbc/authentication/CachingSha2PasswordPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/

package com.mysql.jdbc.authentication;

import java.io.UnsupportedEncodingException;
import java.security.DigestException;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

import com.mysql.jdbc.Buffer;
import com.mysql.jdbc.Connection;
import com.mysql.jdbc.ExportControlled;
import com.mysql.jdbc.Messages;
import com.mysql.jdbc.MySQLConnection;
import com.mysql.jdbc.MysqlIO;
import com.mysql.jdbc.SQLError;
import com.mysql.jdbc.Security;
import com.mysql.jdbc.StringUtils;

public class CachingSha2PasswordPlugin extends Sha256PasswordPlugin {
public static String PLUGIN_NAME = "caching_sha2_password";

public enum AuthStage {
FAST_AUTH_SEND_SCRAMBLE, FAST_AUTH_READ_RESULT, FAST_AUTH_COMPLETE, FULL_AUTH;
}

private AuthStage stage = AuthStage.FAST_AUTH_SEND_SCRAMBLE;

@Override
public void init(Connection conn, Properties props) throws SQLException {
super.init(conn, props);
this.stage = AuthStage.FAST_AUTH_SEND_SCRAMBLE;
}

@Override
public void destroy() {
this.stage = AuthStage.FAST_AUTH_SEND_SCRAMBLE;
super.destroy();
}

@Override
public String getProtocolPluginName() {
return PLUGIN_NAME;
}

@Override
public boolean nextAuthenticationStep(Buffer fromServer, List<Buffer> toServer) throws SQLException {
toServer.clear();

if (this.password == null || this.password.length() == 0 || fromServer == null) {
// no password
Buffer bresp = new Buffer(new byte[] { 0 });
toServer.add(bresp);

} else {
if (this.stage == AuthStage.FAST_AUTH_SEND_SCRAMBLE) {
// send a scramble for fast auth
this.seed = fromServer.readString();
try {
toServer.add(new Buffer(Security.scrambleCachingSha2(StringUtils.getBytes(this.password, this.connection.getPasswordCharacterEncoding()),
this.seed.getBytes())));
} catch (DigestException e) {
throw SQLError.createSQLException(e.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR, e, null);
} catch (UnsupportedEncodingException e) {
throw SQLError.createSQLException(e.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR, e, null);
}
this.stage = AuthStage.FAST_AUTH_READ_RESULT;
return true;

} else if (this.stage == AuthStage.FAST_AUTH_READ_RESULT) {
int fastAuthResult = fromServer.getByteBuffer()[0];
switch (fastAuthResult) {
case 3:
this.stage = AuthStage.FAST_AUTH_COMPLETE;
return true;
case 4:
this.stage = AuthStage.FULL_AUTH;
break;
default:
throw SQLError.createSQLException("Unknown server response after fast auth.", SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE,
this.connection.getExceptionInterceptor());
}
}

if (((MySQLConnection) this.connection).getIO().isSSLEstablished()) {
// allow plain text over SSL
Buffer bresp;
try {
bresp = new Buffer(StringUtils.getBytes(this.password, this.connection.getPasswordCharacterEncoding()));
} catch (UnsupportedEncodingException e) {
throw SQLError.createSQLException(
Messages.getString("Sha256PasswordPlugin.3", new Object[] { this.connection.getPasswordCharacterEncoding() }),
SQLError.SQL_STATE_GENERAL_ERROR, null);
}
bresp.setPosition(bresp.getBufLength());
int oldBufLength = bresp.getBufLength();
bresp.writeByte((byte) 0);
bresp.setBufLength(oldBufLength + 1);
bresp.setPosition(0);
toServer.add(bresp);

} else if (this.connection.getServerRSAPublicKeyFile() != null) {
// encrypt with given key, don't use "Public Key Retrieval"
Buffer bresp = new Buffer(encryptPassword(this.password, this.seed, this.connection, this.publicKeyString));
toServer.add(bresp);

} else {
if (!this.connection.getAllowPublicKeyRetrieval()) {
throw SQLError.createSQLException(Messages.getString("Sha256PasswordPlugin.2"), SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE,
this.connection.getExceptionInterceptor());
}

// We must request the public key from the server to encrypt the password
if (this.publicKeyRequested && fromServer.getBufLength() > MysqlIO.SEED_LENGTH) {
// Servers affected by Bug#70865 could send Auth Switch instead of key after Public Key Retrieval,
// so we check payload length to detect that.

// read key response
Buffer bresp = new Buffer(encryptPassword(this.password, this.seed, this.connection, fromServer.readString()));
toServer.add(bresp);
this.publicKeyRequested = false;
} else {
// build and send Public Key Retrieval packet
Buffer bresp = new Buffer(new byte[] { 2 }); //was 1 in sha256_password
toServer.add(bresp);
this.publicKeyRequested = true;
}
}
}
return true;
}

private static byte[] encryptPassword(String password, String seed, Connection connection, String key) throws SQLException {
byte[] input = null;
try {
input = password != null ? StringUtils.getBytesNullTerminated(password, connection.getPasswordCharacterEncoding()) : new byte[] { 0 };
} catch (UnsupportedEncodingException e) {
throw SQLError.createSQLException(Messages.getString("Sha256PasswordPlugin.3", new Object[] { connection.getPasswordCharacterEncoding() }),
SQLError.SQL_STATE_GENERAL_ERROR, null);
}
byte[] mysqlScrambleBuff = new byte[input.length];
Security.xorString(input, mysqlScrambleBuff, seed.getBytes(), input.length);

return ExportControlled.encryptWithRSAPublicKey(mysqlScrambleBuff,
ExportControlled.decodeRSAPublicKey(key, ((MySQLConnection) connection).getExceptionInterceptor()), "RSA/ECB/PKCS1Padding",
((MySQLConnection) connection).getExceptionInterceptor());
}

@Override
public void reset() {
this.stage = AuthStage.FAST_AUTH_SEND_SCRAMBLE;
}
}
12 changes: 6 additions & 6 deletions src/com/mysql/jdbc/authentication/Sha256PasswordPlugin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
Expand Down Expand Up @@ -49,11 +49,11 @@
public class Sha256PasswordPlugin implements AuthenticationPlugin {
public static String PLUGIN_NAME = "sha256_password";

private Connection connection;
private String password = null;
private String seed = null;
private boolean publicKeyRequested = false;
private String publicKeyString = null;
protected Connection connection;
protected String password = null;
protected String seed = null;
protected boolean publicKeyRequested = false;
protected String publicKeyString = null;

public void init(Connection conn, Properties props) throws SQLException {
this.connection = conn;
Expand Down
4 changes: 2 additions & 2 deletions src/testsuite/BaseTestCase.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
Expand Down Expand Up @@ -651,9 +651,9 @@ public void setUp() throws Exception {
try {
Properties props = new Properties();
props.setProperty("useSSL", "false"); // testsuite is built upon non-SSL default connection
props.setProperty("allowPublicKeyRetrieval", "true");
this.conn = DriverManager.getConnection(dbUrl, props);

props.setProperty("allowPublicKeyRetrieval", "true");
this.sha256Conn = sha256Url == null ? null : DriverManager.getConnection(sha256Url, props);
} catch (Exception ex) {
ex.printStackTrace();
Expand Down
Loading

0 comments on commit 500199b

Please sign in to comment.