Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
/** Current limit for this particular connection. */
private Boolean enablePrepareOnFirstPreparedStatementCall = null;

/** Used for toggling use of sp_prepare */
private String prepareMethod = null;

/** Handle the actual queue of discarded prepared statements. */
Expand Down Expand Up @@ -1963,24 +1964,31 @@ Connection connectInternal(Properties propsIn,
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (null != sPropValue) {
enclaveAttestationProtocol = sPropValue;
if (!AttestationProtocol.isValidAttestationProtocol(enclaveAttestationProtocol)) {
throw new SQLServerException(
SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol"), null);
}

if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.HGS.toString())) {
this.enclaveProvider = new SQLServerVSMEnclaveProvider();
} else {
// If it's a valid Provider & not HGS, then it has to be AAS
} else if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString())) {
this.enclaveProvider = new SQLServerNoneEnclaveProvider();
} else if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.AAS.toString())) {
this.enclaveProvider = new SQLServerAASEnclaveProvider();
} else {
throw new SQLServerException(
SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol"), null);
}
}

// enclave requires columnEncryption=enabled, enclaveAttestationUrl and enclaveAttestationProtocol
if ((null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
if (
// An attestation URL requires a protocol
(null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
&& (null == enclaveAttestationProtocol || enclaveAttestationProtocol.isEmpty()))

// An attestation protocol that is not NONE requires a URL
|| (null != enclaveAttestationProtocol && !enclaveAttestationProtocol.isEmpty()
&& !enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString())
&& (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty()))

// An attestation protocol also requires column encryption
|| (null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
&& (null != enclaveAttestationProtocol || !enclaveAttestationProtocol.isEmpty())
&& (null == columnEncryptionSetting || !isColumnEncryptionSettingEnabled()))) {
Expand Down Expand Up @@ -4506,7 +4514,8 @@ int writeAEFeatureRequest(boolean write, /* if false just calculates the length
if (write) {
tdsWriter.writeByte(TDS.TDS_FEATURE_EXT_AE); // FEATUREEXT_TC
tdsWriter.writeInt(1); // length of version
if (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty()) {
if (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty() || (enclaveAttestationProtocol != null
&& !enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString()))) {
tdsWriter.writeByte(TDS.COLUMNENCRYPTION_VERSION1);
} else {
tdsWriter.writeByte(TDS.COLUMNENCRYPTION_VERSION2);
Expand Down Expand Up @@ -5625,7 +5634,8 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept

serverColumnEncryptionVersion = ColumnEncryptionVersion.AE_V1;

if (null != enclaveAttestationUrl) {
if (null != enclaveAttestationUrl || (enclaveAttestationProtocol != null
&& enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString()))) {
if (aeVersion < TDS.COLUMNENCRYPTION_VERSION2) {
throw new SQLServerException(SQLServerException.getErrString("R_enclaveNotSupported"), null);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ static boolean isValidEncryptOption(String option) {

enum AttestationProtocol {
HGS("HGS"),
AAS("AAS");
AAS("AAS"),
NONE("NONE");

private final String protocol;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/

package com.microsoft.sqlserver.jdbc;

import static java.nio.charset.StandardCharsets.UTF_16LE;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;


/**
*
* Provides the implementation of the NONE Enclave Provider. This enclave provider does not use attestation.
*
*/
public class SQLServerNoneEnclaveProvider implements ISQLServerEnclaveProvider {

private static final EnclaveSessionCache enclaveCache = new EnclaveSessionCache();

private NoneAttestationParameters noneParams = null;
private NoneAttestationResponse noneResponse = null;
private String attestationUrl = null;
private EnclaveSession enclaveSession = null;

@Override
public void getAttestationParameters(String url) throws SQLServerException {
if (null == noneParams) {
attestationUrl = url;
try {
noneParams = new NoneAttestationParameters(attestationUrl);
} catch (IOException e) {
SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false);
}
}
}

@Override
public ArrayList<byte[]> createEnclaveSession(SQLServerConnection connection, SQLServerStatement statement,
String userSql, String preparedTypeDefinitions, Parameter[] params,
ArrayList<String> parameterNames) throws SQLServerException {

/*
* for None attestation: enclave does not send public key, and sends an empty attestation info. The only
* non-trivial content it sends is the session setup info (DH pubkey of enclave).
*/

// Check if the session exists in our cache
StringBuilder keyLookup = new StringBuilder(connection.getServerName()).append(connection.getCatalog())
.append(attestationUrl);
EnclaveCacheEntry entry = enclaveCache.getSession(keyLookup.toString());
if (null != entry) {
this.enclaveSession = entry.getEnclaveSession();
this.noneParams = (NoneAttestationParameters) entry.getBaseAttestationRequest();
}
ArrayList<byte[]> b = describeParameterEncryption(connection, statement, userSql, preparedTypeDefinitions,
params, parameterNames);
if (connection.enclaveEstablished()) {
return b;
} else if (null != noneResponse && !connection.enclaveEstablished()) {
try {
enclaveSession = new EnclaveSession(noneResponse.getSessionID(),
noneParams.createSessionSecret(noneResponse.getDHpublicKey()));
enclaveCache.addEntry(connection.getServerName(), connection.getCatalog(),
connection.enclaveAttestationUrl, noneParams, enclaveSession);
} catch (GeneralSecurityException e) {
SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false);
}
}
return b;
}

@Override
public void invalidateEnclaveSession() {
if (null != enclaveSession) {
enclaveCache.removeEntry(enclaveSession);
}
enclaveSession = null;
noneParams = null;
attestationUrl = null;
}

@Override
public EnclaveSession getEnclaveSession() {
return enclaveSession;
}

private void validateAttestationResponse() throws SQLServerException {
if (null != noneResponse) {
try {
noneResponse.validateDHPublicKey();
} catch (GeneralSecurityException e) {
SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false);
}
}
}

private ArrayList<byte[]> describeParameterEncryption(SQLServerConnection connection, SQLServerStatement statement,
String userSql, String preparedTypeDefinitions, Parameter[] params,
ArrayList<String> parameterNames) throws SQLServerException {
ArrayList<byte[]> enclaveRequestedCEKs = new ArrayList<>();
try (PreparedStatement stmt = connection.prepareStatement(connection.enclaveEstablished() ? SDPE1 : SDPE2)) {
try (ResultSet rs = connection.enclaveEstablished() ? executeSDPEv1(stmt, userSql,
preparedTypeDefinitions) : executeSDPEv2(stmt, userSql, preparedTypeDefinitions, noneParams)) {
if (null == rs) {
// No results. Meaning no parameter.
// Should never happen.
return enclaveRequestedCEKs;
}
processSDPEv1(userSql, preparedTypeDefinitions, params, parameterNames, connection, statement, stmt, rs,
enclaveRequestedCEKs);
// Process the third result set.
if (connection.isAEv2() && stmt.getMoreResults()) {
try (ResultSet hgsRs = stmt.getResultSet()) {
if (hgsRs.next()) {
noneResponse = new NoneAttestationResponse(hgsRs.getBytes(1));
// This validates and establishes the enclave session if valid
validateAttestationResponse();
} else {
SQLServerException.makeFromDriverError(null, this,
SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), "0", false);
}
}
}
}
} catch (SQLException | IOException e) {
if (e instanceof SQLServerException) {
throw (SQLServerException) e;
} else {
throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), null,
0, e);
}
}
return enclaveRequestedCEKs;
}
}


/**
*
* Represents the serialization of the request the client sends to the
* SQL Server while setting up a session.
*
*/
class NoneAttestationParameters extends BaseAttestationRequest {

// Type 2 is NONE, sent as Little Endian 0x20000000
private static final byte[] ENCLAVE_TYPE = new byte[] {0x2, 0x0, 0x0, 0x0};
// Nonce length is always 256
private static final byte[] NONCE_LENGTH = new byte[] {0x0, 0x1, 0x0, 0x0};
private final byte[] nonce = new byte[256];

NoneAttestationParameters(String attestationUrl) throws SQLServerException, IOException {
byte[] attestationUrlBytes = (attestationUrl + '\0').getBytes(UTF_16LE);

ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(attestationUrlBytes.length).array());
os.write(attestationUrlBytes);
os.write(NONCE_LENGTH);
new SecureRandom().nextBytes(nonce);
os.write(nonce);
enclaveChallenge = os.toByteArray();

initBcryptECDH();
}

@Override
byte[] getBytes() throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(ENCLAVE_TYPE);
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(enclaveChallenge.length).array());
os.write(enclaveChallenge);
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ENCLAVE_LENGTH).array());
os.write(ECDH_MAGIC);
os.write(x);
os.write(y);
return os.toByteArray();
}

byte[] getNonce() {
return nonce;
}
}


/**
*
* Represents the deserialization of the byte payload the client receives from the
* SQL Server while setting up a session.
*
*/
class NoneAttestationResponse extends BaseAttestationResponse {

NoneAttestationResponse(byte[] b) throws SQLServerException {
/*-
* Protocol format:
* 1. Total Size of the attestation blob as UINT
* 2. Size of Enclave RSA public key as UINT
* 3. Size of Attestation token as UINT
* 4. Enclave Type as UINT
* 5. Enclave RSA public key (raw key, of length #2)
* 6. Attestation token (of length #3)
* 7. Size of Session ID was UINT
* 8. Session id value
* 9. Size of enclave ECDH public key
* 10. Enclave ECDH public key (of length #9)
*/
ByteBuffer response = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
this.totalSize = response.getInt();
this.identitySize = response.getInt();
this.attestationTokenSize = response.getInt();
this.enclaveType = response.getInt(); // 1 for VBS, 2 for SGX

enclavePK = new byte[identitySize];
byte[] attestationToken = new byte[attestationTokenSize];

response.get(enclavePK, 0, identitySize);
response.get(attestationToken, 0, attestationTokenSize);

this.sessionInfoSize = response.getInt();
response.get(sessionID, 0, 8);
this.DHPKsize = response.getInt();
this.DHPKSsize = response.getInt();

DHpublicKey = new byte[DHPKsize];
publicKeySig = new byte[DHPKSsize];

response.get(DHpublicKey, 0, DHPKsize);
response.get(publicKeySig, 0, DHPKSsize);

if (0 != response.remaining()) {
SQLServerException.makeFromDriverError(null, this,
SQLServerResource.getResource("R_EnclaveResponseLengthError"), "0", false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ public static void setAEConnectionString(String url, String protocol) throws Exc
connectionStringEnclave = TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationUrl",
(null != url) ? url : "http://blah");

// NONE protocol does not need a URL and will not work properly with tests (false negative)
connectionStringEnclave = TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationProtocol",
(null != url) ? protocol : "HGS");
}
Expand Down Expand Up @@ -482,7 +483,10 @@ private static void verifyEnclaveEnabled(Connection con, String protocol) throws
"SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';")) {
while (rs.next()) {
String enclaveType = rs.getString(2);
if (String.valueOf(AttestationProtocol.HGS).equals(protocol)) {

// HGS/NONE use only VBS, AAS can use either VBS or SGX
if (String.valueOf(AttestationProtocol.HGS).equals(protocol)
|| String.valueOf(AttestationProtocol.NONE).equals(protocol)) {
assertEquals(EnclaveType.VBS.getValue(), Integer.parseInt(enclaveType));
} else if (String.valueOf(AttestationProtocol.AAS).equals(protocol)) {
assertTrue(Integer.parseInt(enclaveType) == EnclaveType.VBS.getValue()
Expand Down