Skip to content

Commit e6379de

Browse files
committed
#858 Fix: Legacy authentication breaks non-ASCII characters in passwords
1 parent 2141478 commit e6379de

File tree

12 files changed

+115
-22
lines changed

12 files changed

+115
-22
lines changed

src/main/org/firebirdsql/ds/AbstractConnectionPropertiesDataSource.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,16 @@ public void setSocketFactory(@Nullable String socketFactory) {
441441
FirebirdConnectionProperties.super.setSocketFactory(socketFactory);
442442
}
443443

444+
@Override
445+
public String getLegacyAuthCharset() {
446+
return FirebirdConnectionProperties.super.getLegacyAuthCharset();
447+
}
448+
449+
@Override
450+
public void setLegacyAuthCharset(@Nullable String legacyAuthCharset) {
451+
FirebirdConnectionProperties.super.setLegacyAuthCharset(legacyAuthCharset);
452+
}
453+
444454
@Override
445455
public boolean isUseCatalogAsPackage() {
446456
return FirebirdConnectionProperties.super.isUseCatalogAsPackage();

src/main/org/firebirdsql/gds/ng/wire/auth/ClientAuthBlock.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public ClientAuthBlock(IAttachProperties<?> attachProperties) throws SQLExceptio
6666
return attachProperties.getPassword();
6767
}
6868

69+
public String getLegacyAuthCharset() {
70+
return attachProperties.getLegacyAuthCharset();
71+
}
72+
6973
public boolean isAuthComplete() {
7074
return authComplete;
7175
}

src/main/org/firebirdsql/gds/ng/wire/auth/legacy/LegacyAuthenticationPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public AuthStatus authenticate(ClientAuthBlock clientAuthBlock) {
2929
if (clientAuthBlock.getLogin() == null || clientAuthBlock.getPassword() == null) {
3030
return AuthStatus.AUTH_CONTINUE;
3131
}
32-
clientData = LegacyHash.fbCrypt(clientAuthBlock.getPassword());
32+
clientData = LegacyHash.fbCrypt(clientAuthBlock.getPassword(), clientAuthBlock.getLegacyAuthCharset());
3333
return AuthStatus.AUTH_SUCCESS;
3434
}
3535

src/main/org/firebirdsql/gds/ng/wire/auth/legacy/LegacyHash.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
import org.jspecify.annotations.Nullable;
1313

14+
import java.nio.charset.Charset;
15+
import java.nio.charset.StandardCharsets;
16+
1417
/**
1518
* Implements the one way password hash used by the legacy authentication of Firebird.
1619
* <p>
@@ -363,21 +366,25 @@ private static void initPerm(long[][] perm, byte[] p) {
363366
}
364367

365368
/**
366-
* Encrypts String into crypt (Unix) code as used by Firebird.
369+
* Encrypts {@code key} into UnixCrypt hash as used by Firebird Legacy_Auth.
367370
*
368371
* @param key
369372
* the key to be encrypted
373+
* @param charsetName
374+
* character set to convert {@code key} to bytes
370375
* @return the encrypted String
376+
* @since 7
371377
*/
372-
public static byte[] fbCrypt(@Nullable String key) {
378+
public static byte[] fbCrypt(@Nullable String key, String charsetName) {
373379
if (key == null) {
374-
return new byte[] { '*' }; // will NOT match under ANY circumstances!
380+
// will NOT match under ANY circumstances!
381+
return new byte[] { '*' };
375382
}
376-
377-
final int keyLen = key.length();
383+
byte[] keyBytes = key.getBytes(toCharset(charsetName));
384+
final int keyLen = keyBytes.length;
378385
long keyword = 0L;
379386
for (int i = 0; i < 8; i++) {
380-
keyword = keyword << 8 | ((i < keyLen) ? 2 * key.charAt(i) : 0);
387+
keyword = keyword << 8 | ((i < keyLen) ? 2 * (keyBytes[i] & 0xFF) : 0);
381388
}
382389

383390
long rsltblock = desCipher(desSetKey(keyword));
@@ -393,5 +400,15 @@ public static byte[] fbCrypt(@Nullable String key) {
393400
return cryptResult;
394401
}
395402

403+
private static Charset toCharset(String charsetName) {
404+
try {
405+
return Charset.forName(charsetName);
406+
} catch (IllegalArgumentException e) {
407+
System.getLogger(LegacyHash.class.getName()).log(System.Logger.Level.WARNING,
408+
() -> "Invalid character set %s, falling back to UTF-8".formatted(charsetName), e);
409+
return StandardCharsets.UTF_8;
410+
}
411+
}
412+
396413
}
397414

src/main/org/firebirdsql/gds/ng/wire/version10/V10ParameterConverter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Copyright 2014-2023 Mark Rotteveel
1+
// SPDX-FileCopyrightText: Copyright 2014-2026 Mark Rotteveel
22
// SPDX-License-Identifier: LGPL-2.1-or-later
33
package org.firebirdsql.gds.ng.wire.version10;
44

@@ -30,7 +30,8 @@ protected void populateAuthenticationProperties(final AbstractConnection<?, ?> c
3030
pb.addArgument(tagMapping.getUserNameTag(), props.getUser());
3131
}
3232
if (props.getPassword() != null) {
33-
pb.addArgument(tagMapping.getEncryptedPasswordTag(), LegacyHash.fbCrypt(props.getPassword()));
33+
pb.addArgument(tagMapping.getEncryptedPasswordTag(),
34+
LegacyHash.fbCrypt(props.getPassword(), props.getLegacyAuthCharset()));
3435
}
3536
}
3637

src/main/org/firebirdsql/jaybird/props/AttachmentProperties.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.firebirdsql.gds.ng.WireCrypt;
77
import org.jspecify.annotations.Nullable;
88

9+
import java.nio.charset.Charset;
10+
911
import static org.firebirdsql.jaybird.props.PropertyConstants.DEFAULT_WIRE_COMPRESSION;
1012

1113
/**
@@ -477,4 +479,40 @@ default void setSocketFactory(@Nullable String socketFactory) {
477479
setProperty(PropertyNames.socketFactory, socketFactory);
478480
}
479481

482+
/**
483+
* The Java character set to use when processing a password for Legacy_Auth in the pure Java protocol.
484+
* <p>
485+
* In general, this should be {@code UTF-8} (the default). Using a different character set may be needed when
486+
* connecting to Firebird 2.5 and older when using passwords with non-ASCII characters.
487+
* </p>
488+
*
489+
* @return Java character set name (default {@code UTF-8})
490+
* @since 7
491+
*/
492+
default String getLegacyAuthCharset() {
493+
return getProperty(PropertyNames.legacyAuthCharset, PropertyConstants.DEFAULT_LEGACY_AUTH_CHARSET);
494+
}
495+
496+
/**
497+
* Sets the Java character set to use when processing a password for Legacy_Auth in the pure Java protocol.
498+
* <p>
499+
* In general, this should be {@code UTF-8} (the default). Using a different character set may be needed when
500+
* connecting to Firebird 2.5 and older when using passwords with non-ASCII characters.
501+
* </p>
502+
*
503+
* @param legacyAuthCharset
504+
* Java character set name (use {@code null} to reset to the default, {@code UTF-8})
505+
* @throws java.nio.charset.IllegalCharsetNameException
506+
* if {@code legacyAuthCharset} is an illegal character set name
507+
* @throws java.nio.charset.UnsupportedCharsetException
508+
* if {@code legacyAuth} is not supported
509+
* @since 7
510+
*/
511+
default void setLegacyAuthCharset(@Nullable String legacyAuthCharset) {
512+
if (legacyAuthCharset != null) {
513+
Charset.forName(legacyAuthCharset);
514+
}
515+
setProperty(PropertyNames.legacyAuthCharset, legacyAuthCharset);
516+
}
517+
480518
}

src/main/org/firebirdsql/jaybird/props/PropertyConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public final class PropertyConstants {
7171
public static final String SESSION_TIME_ZONE_SERVER = "server";
7272

7373
public static final String DEFAULT_AUTH_PLUGINS = "Srp256,Srp";
74+
static final String DEFAULT_LEGACY_AUTH_CHARSET = "UTF-8";
7475

7576
private PropertyConstants() {
7677
// no instances

src/main/org/firebirdsql/jaybird/props/PropertyNames.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public final class PropertyNames {
4141
public static final String enableProtocol = "enableProtocol";
4242
public static final String parallelWorkers = "parallelWorkers";
4343
public static final String socketFactory = "socketFactory";
44+
public static final String legacyAuthCharset = "legacyAuthCharset";
4445

4546
// database connection
4647
public static final String sqlDialect = "sqlDialect";

src/main/org/firebirdsql/jaybird/props/internal/StandardConnectionPropertyDefiner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public Stream<ConnectionProperty> defineProperties() {
6363
builder(parallelWorkers).type(INT).aliases("parallel_workers", "isc_dpb_parallel_workers")
6464
.dpbItem(isc_dpb_parallel_workers),
6565
builder(socketFactory),
66+
builder(legacyAuthCharset).aliases("legacyAuthCharSet"),
6667

6768
// Database properties
6869
builder(charSet).aliases("charset", "localEncoding", "local_encoding"),

src/test/org/firebirdsql/common/extension/DatabaseUserExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static DatabaseUserExtension withDatabaseUser() {
5858
public void createUser(String username, String password, @Nullable String plugin) throws SQLException {
5959
requireNonNull(username, "userName");
6060
requireNonNull(password, "password");
61-
try (var connection = getConnectionViaDriverManager();
61+
try (var connection = getConnectionViaDriverManager("lc_ctype", "UTF8");
6262
var statement = connection.createStatement()) {
6363
var createUserSql = new StringBuilder("CREATE USER ").append(statement.enquoteIdentifier(username, false))
6464
.append(" PASSWORD ").append(statement.enquoteLiteral(password));
@@ -76,7 +76,7 @@ public void afterEach(ExtensionContext context) {
7676
if (createdUsers.isEmpty()) {
7777
return;
7878
}
79-
try (var connection = getConnectionViaDriverManager()) {
79+
try (var connection = getConnectionViaDriverManager("lc_ctype", "UTF8")) {
8080
connection.setAutoCommit(false);
8181
try (var statement = connection.createStatement()) {
8282
for (User user : createdUsers) {

0 commit comments

Comments
 (0)