Skip to content

Commit c448bd7

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

File tree

11 files changed

+125
-10
lines changed

11 files changed

+125
-10
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ Changes per Jaybird 6 release.
3333
See also <<whats-new-in-jaybird-6>>.
3434
For known issues, consult <<known-issues>>.
3535

36+
=== Jaybird 6.0.6
37+
38+
The following was fixed or changed since Jaybird 6.0.6:
39+
40+
* Fixed: Legacy authentication fails with non-ASCII characters in password (https://github.com/FirebirdSQL/jaybird/issues/858[#858])
41+
+
42+
The hash that is sent instead of the actual password -- for authentication plugin `Legacy_Auth` or on Firebird 2.5 or older -- was incorrectly generated based on the `char` (UTF-16) value.
43+
We now use UTF-8 bytes instead.
44+
+
45+
See also <<compat-legacy-auth>>.
46+
3647
=== Jaybird 6.0.5
3748

3849
The following was fixed or changed since Jaybird 6.0.4:
@@ -2357,3 +2368,15 @@ Be aware that this dependency does not support `embedded`.
23572368
For more information about this library, see https://github.com/mrotteveel/jaybird-fbclient[^].
23582369

23592370
In the future we may provide JARs with the embedded libraries of a specific Firebird version.
2371+
2372+
[#compat-legacy-auth]
2373+
=== Legacy authentication in Jaybird 6.0.6 and higher
2374+
2375+
Since Jaybird 6.0.6 (and Jaybird 5.0.13), legacy authentication -- Firebird 2.5 or older, or using authentication plugin `Legacy_Auth` -- uses UTF-8 to derive the bytes for calculating the hash of the password to send to the server.
2376+
It is possible UTF-8 is not the right character set on Firebird 2.5 and older.
2377+
This possibly also applies for Firebird 3.0 and higher, if the password was set using connection encoding NONE on (Windows) systems where windows-1252 or similar is the default encoding.
2378+
2379+
You can use connection property `legacyAuthCharset` to specify a different Java character set name (for pure Java connections only).
2380+
2381+
If authentication against Firebird 2.5 or older starts to fail for you, then likely you need to specify `legacyAuthCharset=iso-8859-1`.
2382+
Resetting your password, using https://firebirdsql.org/file/documentation/chunk/en/refdocs/fblangref25/fblangref25-security.html#fblangref25-security-auth-alter-user[`ALTER USER`] with connection encoding UTF8, might also resolve such failures.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,16 @@ public void setSocketFactory(String socketFactory) {
456456
FirebirdConnectionProperties.super.setSocketFactory(socketFactory);
457457
}
458458

459+
@Override
460+
public String getLegacyAuthCharset() {
461+
return FirebirdConnectionProperties.super.getLegacyAuthCharset();
462+
}
463+
464+
@Override
465+
public void setLegacyAuthCharset(String legacyAuthCharset) {
466+
FirebirdConnectionProperties.super.setLegacyAuthCharset(legacyAuthCharset);
467+
}
468+
459469
@Override
460470
public boolean isUseCatalogAsPackage() {
461471
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
@@ -80,6 +80,10 @@ public String getPassword() {
8080
return attachProperties.getPassword();
8181
}
8282

83+
public String getLegacyAuthCharset() {
84+
return attachProperties.getLegacyAuthCharset();
85+
}
86+
8387
public boolean isAuthComplete() {
8488
return authComplete;
8589
}

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
@@ -44,7 +44,7 @@ public AuthStatus authenticate(ClientAuthBlock clientAuthBlock) {
4444
if (clientAuthBlock.getLogin() == null || clientAuthBlock.getPassword() == null) {
4545
return AuthStatus.AUTH_CONTINUE;
4646
}
47-
clientData = LegacyHash.fbCrypt(clientAuthBlock.getPassword());
47+
clientData = LegacyHash.fbCrypt(clientAuthBlock.getPassword(), clientAuthBlock.getLegacyAuthCharset());
4848
return AuthStatus.AUTH_SUCCESS;
4949
}
5050

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
*/
2323
package org.firebirdsql.gds.ng.wire.auth.legacy;
2424

25+
import org.jspecify.annotations.Nullable;
26+
27+
import java.nio.charset.Charset;
28+
import java.nio.charset.StandardCharsets;
29+
2530
/**
2631
* Implements the one way password hash used by the legacy authentication of Firebird.
2732
* <p>
@@ -374,21 +379,25 @@ private static void initPerm(long[][] perm, byte[] p) {
374379
}
375380

376381
/**
377-
* Encrypts String into crypt (Unix) code as used by Firebird.
382+
* Encrypts {@code key} into UnixCrypt hash as used by Firebird Legacy_Auth.
378383
*
379384
* @param key
380385
* the key to be encrypted
386+
* @param charsetName
387+
* character set to convert {@code key} to bytes
381388
* @return the encrypted String
389+
* @since 6.0.6
382390
*/
383-
public static byte[] fbCrypt(final String key) {
391+
public static byte[] fbCrypt(@Nullable String key, String charsetName) {
384392
if (key == null) {
385-
return new byte[] { '*' }; // will NOT match under ANY circumstances!
393+
// will NOT match under ANY circumstances!
394+
return new byte[] { '*' };
386395
}
387-
388-
final int keyLen = key.length();
396+
byte[] keyBytes = key.getBytes(toCharset(charsetName));
397+
final int keyLen = keyBytes.length;
389398
long keyword = 0L;
390399
for (int i = 0; i < 8; i++) {
391-
keyword = keyword << 8 | ((i < keyLen) ? 2 * key.charAt(i) : 0);
400+
keyword = keyword << 8 | ((i < keyLen) ? 2 * (keyBytes[i] & 0xFF) : 0);
392401
}
393402

394403
long rsltblock = desCipher(desSetKey(keyword));
@@ -404,5 +413,15 @@ public static byte[] fbCrypt(final String key) {
404413
return cryptResult;
405414
}
406415

416+
private static Charset toCharset(String charsetName) {
417+
try {
418+
return Charset.forName(charsetName);
419+
} catch (IllegalArgumentException e) {
420+
System.getLogger(LegacyHash.class.getName()).log(System.Logger.Level.WARNING,
421+
() -> "Invalid character set %s, falling back to UTF-8".formatted(charsetName), e);
422+
return StandardCharsets.UTF_8;
423+
}
424+
}
425+
407426
}
408427

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ protected void populateAuthenticationProperties(final AbstractConnection<?, ?> c
4646
pb.addArgument(tagMapping.getUserNameTag(), props.getUser());
4747
}
4848
if (props.getPassword() != null) {
49-
pb.addArgument(tagMapping.getEncryptedPasswordTag(), LegacyHash.fbCrypt(props.getPassword()));
49+
pb.addArgument(tagMapping.getEncryptedPasswordTag(),
50+
LegacyHash.fbCrypt(props.getPassword(), props.getLegacyAuthCharset()));
5051
}
5152
}
5253

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.firebirdsql.gds.ng.FbAttachment;
2828
import org.firebirdsql.gds.ng.WireCrypt;
2929

30+
import java.nio.charset.Charset;
31+
3032
import static org.firebirdsql.jaybird.props.PropertyConstants.DEFAULT_WIRE_COMPRESSION;
3133

3234
/**
@@ -495,4 +497,40 @@ default void setSocketFactory(String socketFactory) {
495497
setProperty(PropertyNames.socketFactory, socketFactory);
496498
}
497499

500+
/**
501+
* The Java character set to use when processing a password for Legacy_Auth in the pure Java protocol.
502+
* <p>
503+
* In general, this should be {@code UTF-8} (the default). Using a different character set may be needed when
504+
* connecting to Firebird 2.5 and older when using passwords with non-ASCII characters.
505+
* </p>
506+
*
507+
* @return Java character set name (default {@code UTF-8})
508+
* @since 6.0.6
509+
*/
510+
default String getLegacyAuthCharset() {
511+
return getProperty(PropertyNames.legacyAuthCharset, PropertyConstants.DEFAULT_LEGACY_AUTH_CHARSET);
512+
}
513+
514+
/**
515+
* Sets the Java character set to use when processing a password for Legacy_Auth in the pure Java protocol.
516+
* <p>
517+
* In general, this should be {@code UTF-8} (the default). Using a different character set may be needed when
518+
* connecting to Firebird 2.5 and older when using passwords with non-ASCII characters.
519+
* </p>
520+
*
521+
* @param legacyAuthCharset
522+
* Java character set name (use {@code null} to reset to the default, {@code UTF-8})
523+
* @throws java.nio.charset.IllegalCharsetNameException
524+
* if {@code legacyAuthCharset} is an illegal character set name
525+
* @throws java.nio.charset.UnsupportedCharsetException
526+
* if {@code legacyAuth} is not supported
527+
* @since 6.0.6
528+
*/
529+
default void setLegacyAuthCharset(String legacyAuthCharset) {
530+
if (legacyAuthCharset != null) {
531+
Charset.forName(legacyAuthCharset);
532+
}
533+
setProperty(PropertyNames.legacyAuthCharset, legacyAuthCharset);
534+
}
535+
498536
}

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

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

8686
public static final String DEFAULT_AUTH_PLUGINS = "Srp256,Srp";
87+
static final String DEFAULT_LEGACY_AUTH_CHARSET = "UTF-8";
8788

8889
private PropertyConstants() {
8990
// no instances

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public final class PropertyNames {
6363
public static final String enableProtocol = "enableProtocol";
6464
public static final String parallelWorkers = "parallelWorkers";
6565
public static final String socketFactory = "socketFactory";
66+
public static final String legacyAuthCharset = "legacyAuthCharset";
6667

6768
// database connection
6869
public static final String sqlDialect = "sqlDialect";

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static DatabaseUserExtension withDatabaseUser() {
6666
* for errors creating the user
6767
*/
6868
public void createUser(String username, String password, String plugin) throws SQLException {
69-
try (var connection = getConnectionViaDriverManager();
69+
try (var connection = getConnectionViaDriverManager("lc_ctype", "UTF8");
7070
var statement = connection.createStatement()) {
7171
var createUserSql = new StringBuilder("CREATE USER ").append(statement.enquoteIdentifier(username, false))
7272
.append(" PASSWORD ").append(statement.enquoteLiteral(password));
@@ -84,7 +84,7 @@ public void afterEach(ExtensionContext context) {
8484
if (createdUsers.isEmpty()) {
8585
return;
8686
}
87-
try (var connection = getConnectionViaDriverManager()) {
87+
try (var connection = getConnectionViaDriverManager("lc_ctype", "UTF8")) {
8888
connection.setAutoCommit(false);
8989
try (var statement = connection.createStatement()) {
9090
for (User user : createdUsers) {

0 commit comments

Comments
 (0)