blob: 75cfa04131362678a04c516020d5ae51d9e1a49e [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.plugin.authentication.standard;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.mariadb.jdbc.Configuration;
import org.mariadb.jdbc.client.Context;
import org.mariadb.jdbc.client.ReadableByteBuf;
import org.mariadb.jdbc.client.socket.Reader;
import org.mariadb.jdbc.client.socket.Writer;
import org.mariadb.jdbc.message.server.AuthSwitchPacket;
import org.mariadb.jdbc.plugin.AuthenticationPlugin;
/** Native password implementation */
public class NativePasswordPlugin implements AuthenticationPlugin {
/** plugin name */
public static final String TYPE = "mysql_native_password";
private String authenticationData;
private byte[] seed;
/**
* Encrypts a password.
*
* <p>protocol for authentication is like this:
*
* <ul>
* <li>Server sends a random array of bytes (the seed)
* <li>client makes a sha1 digest of the password
* <li>client hashes the output of 2
* <li>client digests the seed
* <li>client updates the digest with the output from 3
* <li>an xor of the output of 5 and 2 is sent to server
* <li>server does the same thing and verifies that the scrambled passwords match
* </ul>
*
* @param password the password to encrypt
* @param seed the seed to use
* @return a scrambled password
*/
public static byte[] encryptPassword(final CharSequence password, final byte[] seed) {
try {
if (password == null) return new byte[0];
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
byte[] bytePwd = password.toString().getBytes(StandardCharsets.UTF_8);
final byte[] stage1 = messageDigest.digest(bytePwd);
messageDigest.reset();
final byte[] stage2 = messageDigest.digest(stage1);
messageDigest.reset();
messageDigest.update(seed);
messageDigest.update(stage2);
final byte[] digest = messageDigest.digest();
final byte[] returnBytes = new byte[digest.length];
for (int i = 0; i < digest.length; i++) {
returnBytes[i] = (byte) (stage1[i] ^ digest[i]);
}
return returnBytes;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not use SHA-1, failing", e);
}
}
@Override
public String type() {
return TYPE;
}
/**
* Initialized data.
*
* @param authenticationData authentication data (password/token)
* @param seed server provided seed
* @param conf Connection string options
*/
public void initialize(String authenticationData, byte[] seed, Configuration conf) {
this.seed = seed;
this.authenticationData = authenticationData;
}
/**
* Process native password plugin authentication. see
* https://mariadb.com/kb/en/library/authentication-plugin-mysql_native_password/
*
* @param out out stream
* @param in in stream
* @param context connection context
* @return response packet
* @throws IOException if socket error
*/
public ReadableByteBuf process(Writer out, Reader in, Context context) throws IOException {
if (authenticationData == null) {
out.writeEmptyPacket();
} else {
byte[] truncatedSeed = AuthSwitchPacket.getTruncatedSeed(seed);
out.writeBytes(encryptPassword(authenticationData, truncatedSeed));
out.flush();
}
return in.readReusablePacket();
}
}