Skip to content

Commit ef08328

Browse files
Add option for NONE attestation protocol (#1779)
* Option for no attestation * Resolved PR comments: refactoring, comments, removal of uneccessary code * Addressed memory leak concerns in SQLServerNoneEnclaveProvider * Removed the suppression warning (could lead to false negtives), as well, added a comment for prepareMethod in SQLServerConnection (to address warnings) * Removed validation of public key in SQLServerNoneEnclaveProvider (this validation is only necessary for AAS)
1 parent 8e66ac0 commit ef08328

File tree

4 files changed

+272
-11
lines changed

4 files changed

+272
-11
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
123123
/** Current limit for this particular connection. */
124124
private Boolean enablePrepareOnFirstPreparedStatementCall = null;
125125

126+
/** Used for toggling use of sp_prepare */
126127
private String prepareMethod = null;
127128

128129
/** Handle the actual queue of discarded prepared statements. */
@@ -2000,24 +2001,31 @@ Connection connectInternal(Properties propsIn,
20002001
sPropValue = activeConnectionProperties.getProperty(sPropKey);
20012002
if (null != sPropValue) {
20022003
enclaveAttestationProtocol = sPropValue;
2003-
if (!AttestationProtocol.isValidAttestationProtocol(enclaveAttestationProtocol)) {
2004-
throw new SQLServerException(
2005-
SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol"), null);
2006-
}
20072004

20082005
if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.HGS.toString())) {
20092006
this.enclaveProvider = new SQLServerVSMEnclaveProvider();
2010-
} else {
2011-
// If it's a valid Provider & not HGS, then it has to be AAS
2007+
} else if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString())) {
2008+
this.enclaveProvider = new SQLServerNoneEnclaveProvider();
2009+
} else if (enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.AAS.toString())) {
20122010
this.enclaveProvider = new SQLServerAASEnclaveProvider();
2011+
} else {
2012+
throw new SQLServerException(
2013+
SQLServerException.getErrString("R_enclaveInvalidAttestationProtocol"), null);
20132014
}
20142015
}
20152016

20162017
// enclave requires columnEncryption=enabled, enclaveAttestationUrl and enclaveAttestationProtocol
2017-
if ((null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
2018+
if (
2019+
// An attestation URL requires a protocol
2020+
(null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
20182021
&& (null == enclaveAttestationProtocol || enclaveAttestationProtocol.isEmpty()))
2022+
2023+
// An attestation protocol that is not NONE requires a URL
20192024
|| (null != enclaveAttestationProtocol && !enclaveAttestationProtocol.isEmpty()
2025+
&& !enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString())
20202026
&& (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty()))
2027+
2028+
// An attestation protocol also requires column encryption
20212029
|| (null != enclaveAttestationUrl && !enclaveAttestationUrl.isEmpty()
20222030
&& (null != enclaveAttestationProtocol || !enclaveAttestationProtocol.isEmpty())
20232031
&& (null == columnEncryptionSetting || !isColumnEncryptionSettingEnabled()))) {
@@ -4550,7 +4558,8 @@ int writeAEFeatureRequest(boolean write, /* if false just calculates the length
45504558
if (write) {
45514559
tdsWriter.writeByte(TDS.TDS_FEATURE_EXT_AE); // FEATUREEXT_TC
45524560
tdsWriter.writeInt(1); // length of version
4553-
if (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty()) {
4561+
if (null == enclaveAttestationUrl || enclaveAttestationUrl.isEmpty() || (enclaveAttestationProtocol != null
4562+
&& !enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString()))) {
45544563
tdsWriter.writeByte(TDS.COLUMNENCRYPTION_VERSION1);
45554564
} else {
45564565
tdsWriter.writeByte(TDS.COLUMNENCRYPTION_VERSION2);
@@ -5669,7 +5678,8 @@ private void onFeatureExtAck(byte featureId, byte[] data) throws SQLServerExcept
56695678

56705679
serverColumnEncryptionVersion = ColumnEncryptionVersion.AE_V1;
56715680

5672-
if (null != enclaveAttestationUrl) {
5681+
if (null != enclaveAttestationUrl || (enclaveAttestationProtocol != null
5682+
&& enclaveAttestationProtocol.equalsIgnoreCase(AttestationProtocol.NONE.toString()))) {
56735683
if (aeVersion < TDS.COLUMNENCRYPTION_VERSION2) {
56745684
throw new SQLServerException(SQLServerException.getErrString("R_enclaveNotSupported"), null);
56755685
} else {

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ static boolean isValidEncryptOption(String option) {
162162

163163
enum AttestationProtocol {
164164
HGS("HGS"),
165-
AAS("AAS");
165+
AAS("AAS"),
166+
NONE("NONE");
166167

167168
private final String protocol;
168169

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
3+
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
4+
*/
5+
6+
package com.microsoft.sqlserver.jdbc;
7+
8+
import static java.nio.charset.StandardCharsets.UTF_16LE;
9+
10+
import java.io.ByteArrayOutputStream;
11+
import java.io.IOException;
12+
import java.nio.ByteBuffer;
13+
import java.nio.ByteOrder;
14+
import java.security.GeneralSecurityException;
15+
import java.security.SecureRandom;
16+
import java.sql.PreparedStatement;
17+
import java.sql.ResultSet;
18+
import java.sql.SQLException;
19+
import java.util.ArrayList;
20+
21+
22+
/**
23+
*
24+
* Provides the implementation of the NONE Enclave Provider. This enclave provider does not use attestation.
25+
*
26+
*/
27+
public class SQLServerNoneEnclaveProvider implements ISQLServerEnclaveProvider {
28+
29+
private static final EnclaveSessionCache enclaveCache = new EnclaveSessionCache();
30+
31+
private NoneAttestationParameters noneParams = null;
32+
private NoneAttestationResponse noneResponse = null;
33+
private String attestationUrl = null;
34+
private EnclaveSession enclaveSession = null;
35+
36+
@Override
37+
public void getAttestationParameters(String url) throws SQLServerException {
38+
if (null == noneParams) {
39+
attestationUrl = url;
40+
try {
41+
noneParams = new NoneAttestationParameters(attestationUrl);
42+
} catch (IOException e) {
43+
SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false);
44+
}
45+
}
46+
}
47+
48+
@Override
49+
public ArrayList<byte[]> createEnclaveSession(SQLServerConnection connection, SQLServerStatement statement,
50+
String userSql, String preparedTypeDefinitions, Parameter[] params,
51+
ArrayList<String> parameterNames) throws SQLServerException {
52+
53+
/*
54+
* for None attestation: enclave does not send public key, and sends an empty attestation info. The only
55+
* non-trivial content it sends is the session setup info (DH pubkey of enclave).
56+
*/
57+
58+
// Check if the session exists in our cache
59+
StringBuilder keyLookup = new StringBuilder(connection.getServerName()).append(connection.getCatalog())
60+
.append(attestationUrl);
61+
EnclaveCacheEntry entry = enclaveCache.getSession(keyLookup.toString());
62+
if (null != entry) {
63+
this.enclaveSession = entry.getEnclaveSession();
64+
this.noneParams = (NoneAttestationParameters) entry.getBaseAttestationRequest();
65+
}
66+
ArrayList<byte[]> b = describeParameterEncryption(connection, statement, userSql, preparedTypeDefinitions,
67+
params, parameterNames);
68+
if (connection.enclaveEstablished()) {
69+
return b;
70+
} else if (null != noneResponse && !connection.enclaveEstablished()) {
71+
try {
72+
enclaveSession = new EnclaveSession(noneResponse.getSessionID(),
73+
noneParams.createSessionSecret(noneResponse.getDHpublicKey()));
74+
enclaveCache.addEntry(connection.getServerName(), connection.getCatalog(),
75+
connection.enclaveAttestationUrl, noneParams, enclaveSession);
76+
} catch (GeneralSecurityException e) {
77+
SQLServerException.makeFromDriverError(connection, this, e.getLocalizedMessage(), "0", false);
78+
}
79+
}
80+
return b;
81+
}
82+
83+
@Override
84+
public void invalidateEnclaveSession() {
85+
if (null != enclaveSession) {
86+
enclaveCache.removeEntry(enclaveSession);
87+
}
88+
enclaveSession = null;
89+
noneParams = null;
90+
attestationUrl = null;
91+
}
92+
93+
@Override
94+
public EnclaveSession getEnclaveSession() {
95+
return enclaveSession;
96+
}
97+
98+
private void validateAttestationResponse() throws SQLServerException {
99+
if (null != noneResponse) {
100+
try {
101+
noneResponse.validateDHPublicKey();
102+
} catch (GeneralSecurityException e) {
103+
SQLServerException.makeFromDriverError(null, this, e.getLocalizedMessage(), "0", false);
104+
}
105+
}
106+
}
107+
108+
private ArrayList<byte[]> describeParameterEncryption(SQLServerConnection connection, SQLServerStatement statement,
109+
String userSql, String preparedTypeDefinitions, Parameter[] params,
110+
ArrayList<String> parameterNames) throws SQLServerException {
111+
ArrayList<byte[]> enclaveRequestedCEKs = new ArrayList<>();
112+
try (PreparedStatement stmt = connection.prepareStatement(connection.enclaveEstablished() ? SDPE1 : SDPE2)) {
113+
try (ResultSet rs = connection.enclaveEstablished() ? executeSDPEv1(stmt, userSql,
114+
preparedTypeDefinitions) : executeSDPEv2(stmt, userSql, preparedTypeDefinitions, noneParams)) {
115+
if (null == rs) {
116+
// No results. Meaning no parameter.
117+
// Should never happen.
118+
return enclaveRequestedCEKs;
119+
}
120+
processSDPEv1(userSql, preparedTypeDefinitions, params, parameterNames, connection, statement, stmt, rs,
121+
enclaveRequestedCEKs);
122+
// Process the third result set.
123+
if (connection.isAEv2() && stmt.getMoreResults()) {
124+
try (ResultSet hgsRs = stmt.getResultSet()) {
125+
if (hgsRs.next()) {
126+
noneResponse = new NoneAttestationResponse(hgsRs.getBytes(1));
127+
// This validates and establishes the enclave session if valid
128+
validateAttestationResponse();
129+
} else {
130+
SQLServerException.makeFromDriverError(null, this,
131+
SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), "0", false);
132+
}
133+
}
134+
}
135+
}
136+
} catch (SQLException | IOException e) {
137+
if (e instanceof SQLServerException) {
138+
throw (SQLServerException) e;
139+
} else {
140+
throw new SQLServerException(SQLServerException.getErrString("R_UnableRetrieveParameterMetadata"), null,
141+
0, e);
142+
}
143+
}
144+
return enclaveRequestedCEKs;
145+
}
146+
}
147+
148+
149+
/**
150+
*
151+
* Represents the serialization of the request the client sends to the
152+
* SQL Server while setting up a session.
153+
*
154+
*/
155+
class NoneAttestationParameters extends BaseAttestationRequest {
156+
157+
// Type 2 is NONE, sent as Little Endian 0x20000000
158+
private static final byte[] ENCLAVE_TYPE = new byte[] {0x2, 0x0, 0x0, 0x0};
159+
// Nonce length is always 256
160+
private static final byte[] NONCE_LENGTH = new byte[] {0x0, 0x1, 0x0, 0x0};
161+
private final byte[] nonce = new byte[256];
162+
163+
NoneAttestationParameters(String attestationUrl) throws SQLServerException, IOException {
164+
byte[] attestationUrlBytes = (attestationUrl + '\0').getBytes(UTF_16LE);
165+
166+
ByteArrayOutputStream os = new ByteArrayOutputStream();
167+
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(attestationUrlBytes.length).array());
168+
os.write(attestationUrlBytes);
169+
os.write(NONCE_LENGTH);
170+
new SecureRandom().nextBytes(nonce);
171+
os.write(nonce);
172+
enclaveChallenge = os.toByteArray();
173+
174+
initBcryptECDH();
175+
}
176+
177+
@Override
178+
byte[] getBytes() throws IOException {
179+
ByteArrayOutputStream os = new ByteArrayOutputStream();
180+
os.write(ENCLAVE_TYPE);
181+
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(enclaveChallenge.length).array());
182+
os.write(enclaveChallenge);
183+
os.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ENCLAVE_LENGTH).array());
184+
os.write(ECDH_MAGIC);
185+
os.write(x);
186+
os.write(y);
187+
return os.toByteArray();
188+
}
189+
190+
byte[] getNonce() {
191+
return nonce;
192+
}
193+
}
194+
195+
196+
/**
197+
*
198+
* Represents the deserialization of the byte payload the client receives from the
199+
* SQL Server while setting up a session.
200+
*
201+
*/
202+
class NoneAttestationResponse extends BaseAttestationResponse {
203+
204+
NoneAttestationResponse(byte[] b) throws SQLServerException {
205+
/*-
206+
* Protocol format:
207+
* 1. Total Size of the attestation blob as UINT
208+
* 2. Size of Enclave RSA public key as UINT
209+
* 3. Size of Attestation token as UINT
210+
* 4. Enclave Type as UINT
211+
* 5. Enclave RSA public key (raw key, of length #2)
212+
* 6. Attestation token (of length #3)
213+
* 7. Size of Session ID was UINT
214+
* 8. Session id value
215+
* 9. Size of enclave ECDH public key
216+
* 10. Enclave ECDH public key (of length #9)
217+
*/
218+
ByteBuffer response = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN);
219+
this.totalSize = response.getInt();
220+
this.identitySize = response.getInt();
221+
this.attestationTokenSize = response.getInt();
222+
this.enclaveType = response.getInt(); // 1 for VBS, 2 for SGX
223+
224+
enclavePK = new byte[identitySize];
225+
byte[] attestationToken = new byte[attestationTokenSize];
226+
227+
response.get(enclavePK, 0, identitySize);
228+
response.get(attestationToken, 0, attestationTokenSize);
229+
230+
this.sessionInfoSize = response.getInt();
231+
response.get(sessionID, 0, 8);
232+
this.DHPKsize = response.getInt();
233+
this.DHPKSsize = response.getInt();
234+
235+
DHpublicKey = new byte[DHPKsize];
236+
publicKeySig = new byte[DHPKSsize];
237+
238+
response.get(DHpublicKey, 0, DHPKsize);
239+
response.get(publicKeySig, 0, DHPKSsize);
240+
241+
if (0 != response.remaining()) {
242+
SQLServerException.makeFromDriverError(null, this,
243+
SQLServerResource.getResource("R_EnclaveResponseLengthError"), "0", false);
244+
}
245+
}
246+
}

src/test/java/com/microsoft/sqlserver/jdbc/EnclavePackageTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public static void setAEConnectionString(String url, String protocol) throws Exc
208208
connectionStringEnclave = TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationUrl",
209209
(null != url) ? url : "http://blah");
210210

211+
// NONE protocol does not need a URL and will not work properly with tests (false negative)
211212
connectionStringEnclave = TestUtils.addOrOverrideProperty(connectionStringEnclave, "enclaveAttestationProtocol",
212213
(null != url) ? protocol : "HGS");
213214
}
@@ -482,7 +483,10 @@ private static void verifyEnclaveEnabled(Connection con, String protocol) throws
482483
"SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';")) {
483484
while (rs.next()) {
484485
String enclaveType = rs.getString(2);
485-
if (String.valueOf(AttestationProtocol.HGS).equals(protocol)) {
486+
487+
// HGS/NONE use only VBS, AAS can use either VBS or SGX
488+
if (String.valueOf(AttestationProtocol.HGS).equals(protocol)
489+
|| String.valueOf(AttestationProtocol.NONE).equals(protocol)) {
486490
assertEquals(EnclaveType.VBS.getValue(), Integer.parseInt(enclaveType));
487491
} else if (String.valueOf(AttestationProtocol.AAS).equals(protocol)) {
488492
assertTrue(Integer.parseInt(enclaveType) == EnclaveType.VBS.getValue()

0 commit comments

Comments
 (0)