org.firebirdsql.gds.ng.wire.auth.SrpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird-jdk17 Show documentation
Show all versions of jaybird-jdk17 Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// https://github.com/nakagami/pyfirebirdsql/blob/master/firebirdsql/srp.py
package org.firebirdsql.gds.ng.wire.auth;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.VaxEncoding;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.util.ByteArrayHelper;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.SQLException;
import java.util.Arrays;
/**
* @author Hajime Nakagami
*/
public final class SrpClient {
private static final int SRP_KEY_SIZE = 128;
private static final int SRP_SALT_SIZE = 32;
private static final int EXPECTED_AUTH_DATA_LENGTH = (SRP_SALT_SIZE + SRP_KEY_SIZE + 2) * 2;
private static final BigInteger N = new BigInteger("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7", 16);
private static final BigInteger g = new BigInteger("2");
private static final BigInteger k = new BigInteger("1277432915985975349439481660349303019122249719989");
private static final SecureRandom random = new SecureRandom();
private static final byte[] SEPARATOR_BYTES = ":".getBytes(StandardCharsets.UTF_8);
private BigInteger publicKey; /* A */
private BigInteger privateKey; /* a */
private byte[] sessionKey; /* K */
static class KeyPair {
private BigInteger pub, secret;
private KeyPair(BigInteger pub, BigInteger secret) {
this.pub = pub;
this.secret = secret;
}
BigInteger getPublicKey() {
return pub;
}
BigInteger getPrivateKey() {
return secret;
}
}
private static BigInteger fromBigByteArray(byte[] b) {
return new BigInteger(1, b);
}
private static byte[] toBigByteArray(BigInteger n) {
byte[] b = n.toByteArray();
if (b[0] != 0) {
return b;
}
int i = 1;
while (b[i] == 0) {
i++;
}
return Arrays.copyOfRange(b, i, b.length);
}
private static String padHexBinary(String hexString) {
if (hexString.length() % 2 != 0) {
return '0' + hexString;
}
return hexString;
}
private static byte[] sha1(byte[]... ba) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
for (byte[] b : ba) {
md.update(b);
}
return md.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-1 MessageDigest not available", e);
}
}
private static byte[] pad(BigInteger n) {
final byte[] bn = toBigByteArray(n);
if (bn.length > SRP_KEY_SIZE) {
return Arrays.copyOfRange(bn, bn.length - SRP_KEY_SIZE, bn.length);
}
return bn;
}
private static BigInteger getScramble(BigInteger x, BigInteger y) {
return fromBigByteArray(sha1(pad(x), pad(y)));
}
private static BigInteger getSecret() {
return new BigInteger(SRP_KEY_SIZE, random);
}
static byte[] getSalt() {
byte[] b = new byte[SRP_SALT_SIZE];
random.nextBytes(b);
return b;
}
private static BigInteger getUserHash(String user, String password, byte[] salt) {
final byte[] hash1 = sha1(user.toUpperCase().getBytes(StandardCharsets.UTF_8), SEPARATOR_BYTES,
password.getBytes(StandardCharsets.UTF_8));
final byte[] hash2 = sha1(salt, hash1);
return fromBigByteArray(hash2);
}
static KeyPair serverSeed(String user, String password, byte[] salt) {
final BigInteger v = g.modPow(getUserHash(user, password, salt), N);
final BigInteger b = getSecret();
final BigInteger gb = g.modPow(b, N);
final BigInteger kv = k.multiply(v).mod(N);
final BigInteger B = kv.add(gb).mod(N);
return new KeyPair(B, b);
}
static byte[] getServerSessionKey(String user, String password, byte[] salt, BigInteger A, BigInteger B,
BigInteger b) {
final BigInteger u = getScramble(A, B);
final BigInteger v = g.modPow(getUserHash(user, password, salt), N);
final BigInteger vu = v.modPow(u, N);
final BigInteger Avu = A.multiply(vu).mod(N);
final BigInteger sessionSecret = Avu.modPow(b, N);
return sha1(toBigByteArray(sessionSecret));
}
public SrpClient() {
privateKey = getSecret();
publicKey = g.modPow(privateKey, N);
}
public BigInteger getPublicKey() {
return publicKey;
}
public BigInteger getPrivateKey() {
return privateKey;
}
private byte[] getClientSessionKey(String user, String password, byte[] salt, BigInteger serverPublicKey) {
final BigInteger u = getScramble(publicKey, serverPublicKey);
final BigInteger x = getUserHash(user, password, salt);
final BigInteger gx = g.modPow(x, N);
final BigInteger kgx = k.multiply(gx).mod(N);
final BigInteger diff = serverPublicKey.subtract(kgx).mod(N);
final BigInteger ux = u.multiply(x).mod(N);
final BigInteger aux = privateKey.add(ux).mod(N);
final BigInteger sessionSecret = diff.modPow(aux, N);
return sha1(toBigByteArray(sessionSecret));
}
String getPublicKeyHex() {
return ByteArrayHelper.toHexString(pad(publicKey));
}
byte[] clientProof(String user, String password, byte[] salt, BigInteger serverPublicKey) {
final byte[] K = getClientSessionKey(user, password, salt, serverPublicKey);
final BigInteger n1 = fromBigByteArray(sha1(toBigByteArray(N)));
final BigInteger n2 = fromBigByteArray(sha1(toBigByteArray(g)));
final byte[] M = sha1(toBigByteArray(n1.modPow(n2, N)),
sha1(user.toUpperCase().getBytes(StandardCharsets.UTF_8)), salt,
toBigByteArray(publicKey), toBigByteArray(serverPublicKey), K);
sessionKey = K;
return M;
}
byte[] clientProof(String user, String password, byte[] authData) throws SQLException {
if (authData == null || authData.length == 0) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_auth_data).toFlatSQLException();
}
if (authData.length > EXPECTED_AUTH_DATA_LENGTH) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_auth_datalength)
.messageParameter(authData.length)
.messageParameter(EXPECTED_AUTH_DATA_LENGTH)
.messageParameter("data")
.toFlatSQLException();
}
final int saltLength = VaxEncoding.iscVaxInteger2(authData, 0);
if (saltLength > SRP_SALT_SIZE * 2) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_auth_datalength)
.messageParameter(saltLength)
.messageParameter(SRP_SALT_SIZE * 2)
.messageParameter("salt")
.toFlatSQLException();
}
final byte[] salt = Arrays.copyOfRange(authData, 2, saltLength + 2);
final int keyLength = VaxEncoding.iscVaxInteger2(authData, saltLength + 2);
final int serverKeyStart = saltLength + 4;
if (authData.length - serverKeyStart != keyLength) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_auth_datalength)
.messageParameter(keyLength)
.messageParameter(authData.length - serverKeyStart)
.messageParameter("key")
.toFlatSQLException();
}
final String hexServerPublicKey = new String(authData, serverKeyStart, authData.length - serverKeyStart,
StandardCharsets.US_ASCII);
final BigInteger serverPublicKey = new BigInteger(padHexBinary(hexServerPublicKey), 16);
return clientProof(user.toUpperCase(), password, salt, serverPublicKey);
}
public byte[] getSessionKey() {
return sessionKey;
}
}