use of com.mysql.cj.conf.PropertyDefinitions.AuthMech in project aws-mysql-jdbc by awslabs.
the class SecureSessionTest method testAuthMechanisms.
/**
* Tests that PLAIN, MYSQL41, SHA256_MEMORY, and EXTERNAL authentication mechanisms.
*
* @throws Throwable
*/
@Test
public void testAuthMechanisms() throws Throwable {
try {
this.session.sql("CREATE USER IF NOT EXISTS 'testAuthMechNative'@'%' IDENTIFIED WITH mysql_native_password BY 'mysqlnative'").execute();
this.session.sql("GRANT SELECT ON *.* TO 'testAuthMechNative'@'%'").execute();
this.session.sql("CREATE USER IF NOT EXISTS 'testAuthMechSha256'@'%' IDENTIFIED WITH sha256_password BY 'sha256'").execute();
this.session.sql("GRANT SELECT ON *.* TO 'testAuthMechSha256'@'%'").execute();
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
this.session.sql("CREATE USER IF NOT EXISTS 'testAuthMechCachingSha2'@'%' IDENTIFIED WITH caching_sha2_password BY 'cachingsha2'").execute();
this.session.sql("GRANT SELECT ON *.* TO 'testAuthMechCachingSha2'@'%'").execute();
}
final Field sf = SessionImpl.class.getDeclaredField("session");
sf.setAccessible(true);
final Field pf = CoreSession.class.getDeclaredField("protocol");
pf.setAccessible(true);
final Field mf = XAuthenticationProvider.class.getDeclaredField("authMech");
mf.setAccessible(true);
Function<Session, AuthMech> getAuthMech = s -> {
try {
return (AuthMech) mf.get(((XProtocol) pf.get(sf.get(s))).getAuthenticationProvider());
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
};
/*
* Access denied (ER_ACCESS_DENIED_ERROR) error message from servers up to version 8.0.11.
* In MySQL 8.0.12 this message changed due to fix for Bug#27675699 - FAILED AUTHENTICATION AT X PLUGIN ALWAYS RETURNS ER_ACCESS_DENIED_ERROR.
* This variable may be redefined as needed along the test.
*/
String accessDeniedErrMsg = "ERROR 1045 \\(HY000\\) Invalid user or password";
/*
* Authenticate using (default) TLS first. As per MySQL 8.0.4 X Plugin this is required so that SHA2[56] logins get cached in SHA2_MEMORY.
*/
Session testSession = null;
Properties props = new Properties(this.sslFreeTestProperties);
// With default auth mechanism for secure connections (PLAIN).
// *** User: mysqlnative; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechNative");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "mysqlnative");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe & account gets cached.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechSha256; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechSha256");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "sha256");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe & account gets cached.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechSha256", testSession);
testSession.close();
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechCachingSha2; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechCachingSha2");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "cachingsha2");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe & account gets cached.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechCachingSha2", testSession);
testSession.close();
}
// Forcing an auth mechanism.
// *** User: testAuthMechNative; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechNative");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "mysqlnative");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
// *** User: testAuthMechNative; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Matching auth mechanism.
assertEquals(AuthMech.MYSQL41, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// *** User: testAuthMechNative; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechSha256; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechSha256");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "sha256");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechSha256", testSession);
testSession.close();
// *** User: testAuthMechSha256; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.12"))) {
accessDeniedErrMsg = "ERROR 1045 \\(HY000\\) Access denied for user 'testAuthMechSha256'@.*";
}
// Auth mech mismatch.
assertThrows(XProtocolError.class, accessDeniedErrMsg, () -> this.fact.getSession(props));
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// *** User: testAuthMechSha256; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechSha256", testSession);
testSession.close();
}
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechCachingSha2; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechCachingSha2");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "cachingsha2");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Connection is secure, passwords are safe.
assertEquals(AuthMech.PLAIN, getAuthMech.apply(testSession));
assertUser("testAuthMechCachingSha2", testSession);
testSession.close();
// *** User: testAuthMechCachingSha2; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.12"))) {
accessDeniedErrMsg = "ERROR 1045 \\(HY000\\) Access denied for user 'testAuthMechCachingSha2'@.*";
}
// Auth mech mismatch.
assertThrows(XProtocolError.class, accessDeniedErrMsg, () -> this.fact.getSession(props));
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// User: testAuthMechCachingSha2; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechCachingSha2", testSession);
testSession.close();
}
}
// *** User: external; Auth: EXTERNAL.
props.setProperty(PropertyKey.USER.getKeyName(), "external");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "external");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "EXTERNAL");
assertThrows(XProtocolError.class, "ERROR 1251 \\(HY000\\) Invalid authentication method EXTERNAL", () -> this.fact.getSession(props));
props.remove(PropertyKey.xdevapiAuth.getKeyName());
/*
* Authenticate using non-secure connections.
*/
props.setProperty(PropertyKey.xdevapiSslMode.getKeyName(), PropertyDefinitions.XdevapiSslMode.DISABLED.toString());
// With default auth mechanism for non-secure connections (MYSQL41|SHA2_MEMORY).
// *** User: testAuthMechNative; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechNative");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "mysqlnative");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Matching auth mechanism.
assertEquals(AuthMech.MYSQL41, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_PASSWORD requires secure connections in MySQL 8.0.3 and below.
// *** User: testAuthMechSha256; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechSha256");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "sha256");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechSha256", testSession);
testSession.close();
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// CACHING_SHA2_PASSWORD requires secure connections in MySQL 8.0.3 and below.
// *** User: testAuthMechCachingSha2; Auth: default.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechCachingSha2");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "cachingsha2");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechCachingSha2", testSession);
testSession.close();
}
// Forcing an auth mechanism.
// *** User: testAuthMechNative; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechNative");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "mysqlnative");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
assertThrows(XProtocolError.class, "PLAIN authentication is not allowed via unencrypted connection\\.", () -> this.fact.getSession(props));
// *** User: testAuthMechNative; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Matching auth mechanism.
assertEquals(AuthMech.MYSQL41, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// *** User: testAuthMechNative; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechNative", testSession);
testSession.close();
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechSha256; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechSha256");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "sha256");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
assertThrows(XProtocolError.class, "PLAIN authentication is not allowed via unencrypted connection\\.", () -> this.fact.getSession(props));
// *** User: testAuthMechSha256; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.12"))) {
accessDeniedErrMsg = "ERROR 1045 \\(HY000\\) Access denied for user 'testAuthMechSha256'@.*";
}
// Auth mech mismatch.
assertThrows(XProtocolError.class, accessDeniedErrMsg, () -> this.fact.getSession(props));
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// *** User: testAuthMechSha256; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechSha256", testSession);
testSession.close();
}
}
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.3"))) {
// *** User: testAuthMechCachingSha2; Auth: PLAIN.
props.setProperty(PropertyKey.USER.getKeyName(), "testAuthMechCachingSha2");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "cachingsha2");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "PLAIN");
assertThrows(XProtocolError.class, "PLAIN authentication is not allowed via unencrypted connection\\.", () -> this.fact.getSession(props));
// *** User: testAuthMechCachingSha2; Auth: MYSQL41.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "MYSQL41");
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.12"))) {
accessDeniedErrMsg = "ERROR 1045 \\(HY000\\) Access denied for user 'testAuthMechCachingSha2'@.*";
}
// Auth mech mismatch.
assertThrows(XProtocolError.class, accessDeniedErrMsg, () -> this.fact.getSession(props));
if (mysqlVersionMeetsMinimum(ServerVersion.parseVersion("8.0.4"))) {
// SHA256_MEMORY support added in MySQL 8.0.4.
// *** User: testAuthMechCachingSha2; Auth: SHA256_MEMORY.
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "SHA256_MEMORY");
testSession = this.fact.getSession(props);
assertNonSecureSession(testSession);
// Account is cached by now.
assertEquals(AuthMech.SHA256_MEMORY, getAuthMech.apply(testSession));
assertUser("testAuthMechCachingSha2", testSession);
testSession.close();
}
}
// *** User: external; Auth: EXTERNAL.
props.setProperty(PropertyKey.USER.getKeyName(), "external");
props.setProperty(PropertyKey.PASSWORD.getKeyName(), "external");
props.setProperty(PropertyKey.xdevapiAuth.getKeyName(), "EXTERNAL");
assertThrows(XProtocolError.class, "ERROR 1251 \\(HY000\\) Invalid authentication method EXTERNAL", () -> this.fact.getSession(props));
props.remove(PropertyKey.xdevapiAuth.getKeyName());
} finally {
this.session.sql("DROP USER IF EXISTS testAuthMechNative").execute();
this.session.sql("DROP USER IF EXISTS testAuthMechSha256").execute();
this.session.sql("DROP USER IF EXISTS testAuthMechCachingSha2").execute();
}
}
use of com.mysql.cj.conf.PropertyDefinitions.AuthMech in project aws-mysql-jdbc by awslabs.
the class XAuthenticationProvider method changeUser.
@Override
public void changeUser(String userName, String password, String database) {
boolean overTLS = ((XServerCapabilities) this.protocol.getServerSession().getCapabilities()).getTls();
RuntimeProperty<AuthMech> authMechProp = this.protocol.getPropertySet().<AuthMech>getEnumProperty(PropertyKey.xdevapiAuth);
List<AuthMech> tryAuthMech;
if (overTLS || authMechProp.isExplicitlySet()) {
tryAuthMech = Arrays.asList(authMechProp.getValue());
} else {
tryAuthMech = Arrays.asList(AuthMech.MYSQL41, AuthMech.SHA256_MEMORY);
}
XProtocolError capturedAuthErr = null;
for (AuthMech am : tryAuthMech) {
this.authMech = am;
try {
switch(this.authMech) {
case SHA256_MEMORY:
this.protocol.send(this.messageBuilder.buildSha256MemoryAuthStart(), 0);
byte[] nonce = this.protocol.readAuthenticateContinue();
this.protocol.send(this.messageBuilder.buildSha256MemoryAuthContinue(userName, password, nonce, database), 0);
break;
case MYSQL41:
this.protocol.send(this.messageBuilder.buildMysql41AuthStart(), 0);
byte[] salt = this.protocol.readAuthenticateContinue();
this.protocol.send(this.messageBuilder.buildMysql41AuthContinue(userName, password, salt, database), 0);
break;
case PLAIN:
if (overTLS) {
this.protocol.send(this.messageBuilder.buildPlainAuthStart(userName, password, database), 0);
} else {
throw new XProtocolError("PLAIN authentication is not allowed via unencrypted connection.");
}
break;
case EXTERNAL:
this.protocol.send(this.messageBuilder.buildExternalAuthStart(database), 0);
break;
default:
throw new WrongArgumentException("Unknown authentication mechanism '" + this.authMech + "'.");
}
} catch (CJCommunicationsException e) {
if (capturedAuthErr != null && e.getCause() instanceof ClosedChannelException) {
// High probability that the server doesn't support authentication sequences. Ignore this exception and throw the previous one.
throw capturedAuthErr;
}
throw e;
}
try {
this.protocol.readAuthenticateOk();
// Clear any captured exception and stop trying remaining auth mechanisms.
capturedAuthErr = null;
break;
} catch (XProtocolError e) {
if (e.getErrorCode() != MysqlErrorNumbers.ER_ACCESS_DENIED_ERROR) {
throw e;
}
capturedAuthErr = e;
}
}
if (capturedAuthErr != null) {
if (tryAuthMech.size() == 1) {
throw capturedAuthErr;
}
// More than one authentication mechanism was tried.
String errMsg = "Authentication failed using " + StringUtils.joinWithSerialComma(tryAuthMech) + ", check username and password or try a secure connection";
XDevAPIError ex = new XDevAPIError(errMsg, capturedAuthErr);
ex.setVendorCode(capturedAuthErr.getErrorCode());
ex.setSQLState(capturedAuthErr.getSQLState());
throw ex;
}
this.protocol.afterHandshake();
}
Aggregations