Skip to content

Commit

Permalink
Embed auth DLL with driver (#2094)
Browse files Browse the repository at this point in the history
* Embed auth DLL into driver

* Added comment

* Added 32bit DLL

* PR comments

* PR comments pt.2

* PR comments pt.3

* Removed use of unavailable method in Java 8
  • Loading branch information
tkyc committed Mar 29, 2023
1 parent b3a61dd commit c3538fd
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 30 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
</configuration>
</plugin>
</plugins>

</build>
</profile>
</profiles>
Expand All @@ -407,6 +408,9 @@
<include>META-INF/services/java.sql.Driver</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
Expand Down
94 changes: 79 additions & 15 deletions src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@

package com.microsoft.sqlserver.jdbc;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.Date;
import java.util.UUID;
import java.util.logging.Level;


Expand All @@ -25,7 +33,7 @@ class FedAuthDllInfo {
*/
final class AuthenticationJNI extends SSPIAuthentication {
private static final int MAXPOINTERSIZE = 128; // we keep the SNI_Sec pointer
private static boolean enabled = false;
static boolean isDllLoaded = false;
private static java.util.logging.Logger authLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.internals.AuthenticationJNI");
private static int sspiBlobMaxlen = 0;
Expand All @@ -41,36 +49,78 @@ static int getMaxSSPIBlobSize() {
return sspiBlobMaxlen;
}

static boolean isDllLoaded() {
return enabled;
}

static {
UnsatisfiedLinkError temp = null;
// Load the DLL
try {
System.loadLibrary(SQLServerDriver.AUTH_DLL_NAME);
int[] pkg = new int[1];
pkg[0] = 0;

if (0 == SNISecInitPackage(pkg, authLogger)) {
sspiBlobMaxlen = pkg[0];
} else {
throw new UnsatisfiedLinkError();
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableToLoadAuthDllManually"));
throw new UnsatisfiedLinkError(form.format(new Object[] {SQLServerDriver.AUTH_DLL_NAME}));
}
isDllLoaded = true;
} catch (UnsatisfiedLinkError ue) {
// If os is windows, attempt to extract and load the DLL packaged with the driver
if (SQLServerConnection.isWindows) {
String tempDirectory = System.getProperty("java.io.tmpdir") + SQLServerDriver.DLL_NAME + "\\";
File outputDLL = new File(tempDirectory + UUID.randomUUID() + "-" + new Date().getTime() + ".dll");

try {
Files.createDirectories(Paths.get(tempDirectory));

try (InputStream is = AuthenticationJNI.class.getResourceAsStream(
"/" + SQLServerDriver.DLL_NAME + "." + Util.getJVMArchOnWindows() + ".dll")) {
try (FileOutputStream fos = new FileOutputStream(outputDLL)) {
if (is != null) {
int length = is.available();
byte[] buffer = new byte[length];
if ((length = is.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
}
}
}

System.load(outputDLL.getAbsolutePath());

int[] pkg = new int[1];

if (0 == SNISecInitPackage(pkg, authLogger)) {
sspiBlobMaxlen = pkg[0];
} else {
throw new UnsatisfiedLinkError(SQLServerException.getErrString("R_UnableToLoadPackagedAuthDll"));
}

isDllLoaded = true;

} catch (UnsatisfiedLinkError e) {
temp = e; // R_UnableToLoadPackagedAuthDll
} catch (IOException ioe) {
temp = new UnsatisfiedLinkError(ioe.getMessage());
} finally {
Runtime.getRuntime().addShutdownHook(cleanup(tempDirectory));
}
}
enabled = true;
} catch (UnsatisfiedLinkError e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadAuthDll"));
temp = new UnsatisfiedLinkError(form.format(new Object[] {SQLServerDriver.AUTH_DLL_NAME}));
// This is not re-thrown on purpose - the constructor will terminate the properly with the appropriate error
// string

// If temp is still null and DLL is still not enabled, we attempted to load the DLL manually and failed
if (null == temp && !isDllLoaded) {
temp = ue; // R_UnableToLoadAuthDllManually
}

// The errors above are not re-thrown on purpose - the constructor will terminate properly
// with the appropriate error string

} finally {
linkError = temp;
}

}

AuthenticationJNI(SQLServerConnection con, String address, int serverport) throws SQLServerException {
if (!enabled) {
if (!isDllLoaded) {
con.terminate(SQLServerException.DRIVER_ERROR_NONE,
SQLServerException.getErrString("R_notConfiguredForIntegrated"), linkError);
}
Expand Down Expand Up @@ -135,6 +185,20 @@ private static String initDNSArray(String address) {
return dns[0];
}

private static Thread cleanup(String dllDirectory) {
return new Thread(() -> {
File[] files = new File(dllDirectory).listFiles();

if (null != files) {
for (File dll : files) {
// If DLL is still loaded and in use, deletion will fail. So, it is safe
// to iterate and delete all files.
dll.delete();
}
}
});
}

// we use arrays of size one in many places to retrieve output values
// Java Integer objects are immutable so we cant use them to get the output sizes.
// Same for String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,12 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
static final private java.util.logging.Logger windowsCertificateStoreLogger = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider");

static boolean isWindows;

String name = "MSSQL_CERTIFICATE_STORE";

static final String LOCAL_MACHINE_DIRECTORY = "LocalMachine";
static final String CURRENT_USER_DIRECTORY = "CurrentUser";
static final String MY_CERTIFICATE_STORE = "My";

static {
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
isWindows = true;
} else {
isWindows = false;
}
}

/**
* Constructs a SQLServerColumnEncryptionCertificateStoreProvider.
*/
Expand Down Expand Up @@ -71,7 +61,7 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption
windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(),
"decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
byte[] plainCek;
if (isWindows) {
if (SQLServerConnection.isWindows) {
plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm,
encryptedColumnEncryptionKey);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,10 @@ IdleConnectionResiliency getSessionRecovery() {
/** global system ColumnEncryptionKeyStoreProviders */
static Map<String, SQLServerColumnEncryptionKeyStoreProvider> globalSystemColumnEncryptionKeyStoreProviders = new HashMap<>();

static boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");

static {
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
if (isWindows) {
SQLServerColumnEncryptionCertificateStoreProvider provider = new SQLServerColumnEncryptionCertificateStoreProvider();
globalSystemColumnEncryptionKeyStoreProviders.put(provider.getName(), provider);
}
Expand Down Expand Up @@ -5718,8 +5720,7 @@ private SqlAuthenticationToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw
} else if (authenticationString
.equalsIgnoreCase(SqlAuthentication.ACTIVE_DIRECTORY_INTEGRATED.toString())) {
// If operating system is windows and mssql-jdbc_auth is loaded then choose the DLL authentication.
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")
&& AuthenticationJNI.isDllLoaded()) {
if (isWindows && AuthenticationJNI.isDllLoaded) {
try {
FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(
fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,8 @@ public String toString() {
public final class SQLServerDriver implements java.sql.Driver {
static final String PRODUCT_NAME = "Microsoft JDBC Driver " + SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR
+ " for SQL Server";
static final String AUTH_DLL_NAME = "mssql-jdbc_auth-" + SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "."
static final String DLL_NAME = "mssql-jdbc_auth";
static final String AUTH_DLL_NAME = DLL_NAME + "-" + SQLJdbcVersion.MAJOR + "." + SQLJdbcVersion.MINOR + "."
+ SQLJdbcVersion.PATCH + "." + Util.getJVMArchOnWindows() + SQLJdbcVersion.RELEASE_EXT;
static final String DEFAULT_APP_NAME = "Microsoft JDBC Driver for SQL Server";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ protected Object[][] getContents() {
{"R_unassignableError", "The class specified by the {0} property must be assignable to {1}."},
{"R_InvalidCSVQuotes", "Failed to parse the CSV file, verify that the fields are correctly enclosed in double quotes."},
{"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."},
{"R_UnableToLoadPackagedAuthDll", "Unable to load authentication DLL packaged with driver."},
{"R_UnableToLoadAuthDllManually", "Unable to load authentication DLL specified by VM argument java.library.path. Failed to load auth DLL with name {0}."},
{"R_maxResultBufferPropertyDescription", "Determines maximum amount of bytes that can be read during retrieval of result set"},
{"R_maxResultBufferInvalidSyntax", "Invalid syntax: {0} in maxResultBuffer parameter."},
{"R_maxResultBufferNegativeParameterValue", "MaxResultBuffer must have positive value: {0}."},
Expand Down
Binary file added src/main/resources/mssql-jdbc_auth.x64.dll
Binary file not shown.
Binary file added src/main/resources/mssql-jdbc_auth.x86.dll
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import java.sql.SQLException;

import com.microsoft.sqlserver.jdbc.TestUtils;
import com.microsoft.sqlserver.testframework.Constants;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -69,4 +71,20 @@ public void testInvalidConnectWithIPAddressPreference() throws SQLException {
}
}

@Test
@Tag(Constants.xAzureSQLDW)
@Tag(Constants.xAzureSQLDB)
@Tag(Constants.xAzureSQLMI)
public void testConnectionWithIntegratedSecurityWhenAuthDLLIsNotProvided() throws SQLException {
org.junit.Assume.assumeTrue(isWindows);
SQLServerDataSource ds = new SQLServerDataSource();
ds.setURL(connectionString);
ds.setUser("");
ds.setPassword("");
ds.setIntegratedSecurity(true);

// Driver should use packaged auth DLL
try (Connection con = ds.getConnection()) {}
}

}

0 comments on commit c3538fd

Please sign in to comment.