Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for NONE attestation protocol #1779

Merged
merged 7 commits into from
Apr 1, 2022
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()))) {
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
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,
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
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 {
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved

// 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