Skip to content

Commit

Permalink
Feature | Added support for Always Encrypted with Secure Enclaves (#1155
Browse files Browse the repository at this point in the history
)
  • Loading branch information
rene-ye authored and ulvii committed Oct 16, 2019
1 parent 02da869 commit 276d2ca
Show file tree
Hide file tree
Showing 30 changed files with 2,329 additions and 1,160 deletions.
14 changes: 13 additions & 1 deletion src/main/java/com/microsoft/sqlserver/jdbc/AE.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ enum DescribeParameterEncryptionResultSet1 {
EncryptedKey,
ProviderName,
KeyPath,
KeyEncryptionAlgorithm;
KeyEncryptionAlgorithm,
IsRequestedByEnclave,
EnclaveCMKSignature;

int value() {
// Column indexing starts from 1;
Expand All @@ -266,5 +268,15 @@ int value() {
// Column indexing starts from 1;
return ordinal() + 1;
}
}

enum ColumnEncryptionVersion {
AE_NotSupported,
AE_v1,
AE_v2;

int value() {
// Column indexing starts from 1;
return ordinal() + 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,7 @@ private static native FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(Stri

static native byte[] DecryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
byte[] encryptedColumnEncryptionKey) throws DLLException;

static native boolean VerifyColumnMasterKeyMetadata(String keyPath, boolean allowEnclaveComputations,
byte[] signature) throws DLLException;
}
28 changes: 26 additions & 2 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.text.MessageFormat;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
Expand Down Expand Up @@ -113,7 +114,9 @@ final class TDS {
// AE constants
// 0x03 is for x_eFeatureExtensionId_Rcs
static final byte TDS_FEATURE_EXT_AE = 0x04;
static final byte MAX_SUPPORTED_TCE_VERSION = 0x01; // max version
static final byte COLUMNENCRYPTION_NOT_SUPPORTED = 0x00; // column encryption not supported
static final byte COLUMNENCRYPTION_VERSION1 = 0x01; // column encryption without enclave
static final byte COLUMNENCRYPTION_VERSION2 = 0x02; // column encryption with enclave
static final int CUSTOM_CIPHER_ALGORITHM_ID = 0; // max version
// 0x06 is for x_eFeatureExtensionId_LoginToken
// 0x07 is for x_eFeatureExtensionId_ClientSideTelemetry
Expand Down Expand Up @@ -3701,8 +3704,9 @@ void writeBytes(byte[] value, int offset, int length) throws SQLServerException
int bytesWritten = 0;
int bytesToWrite;

if (logger.isLoggable(Level.FINEST))
if (logger.isLoggable(Level.FINEST)) {
logger.finest(toString() + " Writing " + length + " bytes");
}

while ((bytesToWrite = length - bytesWritten) > 0) {
if (0 == stagingBuffer.remaining())
Expand Down Expand Up @@ -6189,6 +6193,22 @@ void writeRPCReaderUnicode(String sName, Reader re, long reLength, boolean bOut,
// Write the data
writeReader(re, reLength, usePLP);
}

void sendEnclavePackage(String sql, ArrayList<byte[]> enclaveCEKs) throws SQLServerException {
if (null != con && con.isAEv2()) {
if (null != sql && "" != sql && null != enclaveCEKs && 0 < enclaveCEKs.size() && con.enclaveEstablished()) {
byte[] b = con.generateEncalvePackage(sql, enclaveCEKs);
if (null != b && 0 != b.length) {
this.writeShort((short) b.length);
this.writeBytes(b);
} else {
this.writeShort((short) 0);
}
} else {
this.writeShort((short) 0);
}
}
}
}


Expand Down Expand Up @@ -6284,6 +6304,7 @@ final SQLServerConnection getConnection() {
private boolean useColumnEncryption = false;
private boolean serverSupportsColumnEncryption = false;
private boolean serverSupportsDataClassification = false;
private ColumnEncryptionVersion columnEncryptionVersion;

private final byte valueBytes[] = new byte[256];

Expand All @@ -6308,6 +6329,7 @@ private static int nextReaderID() {
useColumnEncryption = true;
}
serverSupportsColumnEncryption = con.getServerSupportsColumnEncryption();
columnEncryptionVersion = con.getServerColumnEncryptionVersion();
serverSupportsDataClassification = con.getServerSupportsDataClassification();
}

Expand Down Expand Up @@ -7162,6 +7184,8 @@ final boolean readingResponse() {
return readingResponse;
}

protected ArrayList<byte[]> enclaveCEKs;

/**
* Creates this command with an optional timeout.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,13 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
*/
void setKeyVaultProviderClientKey(String keyVaultProviderClientKey);

/**
* Returns the value for the connection property 'domain'.
*
* @return 'domain' property value
*/
String getDomain();

/**
* Sets the 'domain' connection property used for NTLM Authentication.
*
Expand All @@ -852,13 +859,6 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
*/
void setDomain(String domain);

/**
* Returns the value for the connection property 'domain'.
*
* @return 'domain' property value
*/
String getDomain();

/**
* Returns the current flag value for useFmtOnly.
*
Expand All @@ -873,4 +873,35 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
* boolean value for 'useFmtOnly'.
*/
void setUseFmtOnly(boolean useFmtOnly);

/**
* Returns the enclave attestation URL used in Always Encrypted with Secure Enclaves.
*
* @return enclave attestation URL.
*/
String getEnclaveAttestationUrl();

/**
* Sets the enclave attestation URL used in Always Encrypted with Secure Enclaves.
*
* @param url
* Enclave attestation URL.
*/
void setEnclaveAttestationUrl(String url);

/**
* Returns the enclave attestation protocol used in Always Encrypted with Secure Enclaves.
*
* @return Enclave attestation protocol.
*/
String getEnclaveAttestationProtocol();

/**
* Sets the enclave attestation protocol to be used in Always Encrypted with Secure Enclaves.
*
* @param protocol
* Enclave attestation protocol.
*/
void setEnclaveAttestationProtocol(String protocol);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 java.security.PrivateKey;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
*
* Provides an interface to create an Enclave Session
*
*/
public interface ISQLServerEnclaveProvider {
byte[] getEnclavePackage(String userSQL, ArrayList<byte[]> enclaveCEKs) throws SQLServerException;

/**
* Returns the attestation parameters
* @param createNewParameters
* indicates whether to create new parameters
* @param url
* attestation url
* @throws SQLServerException
* when an error occurs.
*/
void getAttestationParameters(boolean createNewParameters, String url) throws SQLServerException;

/**
* Creates the enclave session
*
* @param connection
* connection
* @param userSql
* user sql
* @param preparedTypeDefinitions
* preparedTypeDefinitions
* @param params
* params
* @param parameterNames
* parameterNames
* @return
* list of enclave requested CEKs
* @throws SQLServerException
* when an error occurs.
*/
ArrayList<byte[]> createEnclaveSession(SQLServerConnection connection, String userSql,
String preparedTypeDefinitions, Parameter[] params,
ArrayList<String> parameterNames) throws SQLServerException;

/**
* Invalidates an enclave session
*/
void invalidateEnclaveSession();

/**
* Returns the enclave session
* @return
* the enclave session
*/
EnclaveSession getEnclaveSession();
}


abstract class BaseAttestationRequest {
protected PrivateKey privateKey;

byte[] getBytes() {
return null;
};
}


class EnclaveSession {
private byte[] sessionID;
private AtomicInteger counter;
private byte[] sessionSecret;

EnclaveSession(byte[] cs, byte[] b) {
sessionID = cs;
sessionSecret = b;
counter = new AtomicInteger(0);
}

byte[] getSessionID() {
return sessionID;
}

byte[] getSessionSecret() {
return sessionSecret;
}

long getCounter() {
return counter.getAndIncrement();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private static byte[] decryptRSAOAEP(byte[] cipherText,

}

private static boolean verifyRSASignature(byte[] hash, byte[] signature, X509Certificate certificate,
static boolean verifyRSASignature(byte[] hash, byte[] signature, X509Certificate certificate,
String masterKeyPath) throws SQLServerException {
Signature signVerify;
boolean verificationSuccess = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1544,13 +1544,14 @@ private TDSWriter sendBulkCopyCommand(TDSCommand command) throws SQLServerExcept
// Create and send the initial command for bulk copy ("INSERT BULK ...").
TDSWriter tdsWriter = command.startRequest(TDS.PKT_QUERY);
String bulkCmd = createInsertBulkCommand(tdsWriter);
tdsWriter.sendEnclavePackage(null, null);
tdsWriter.writeString(bulkCmd);
TDSParser.parse(command.startResponse(), command.getLogContext());

// Send the bulk data. This is the BulkLoadBCP TDS stream.
tdsWriter = command.startRequest(TDS.PKT_BULK);

// Write the COLUMNMETADATA token in the stream.

writeColumnMetaData(tdsWriter);

return tdsWriter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,19 +448,24 @@ private void ValidateNonEmptyAKVPath(String masterKeyPath) throws SQLServerExcep
URI parsedUri = null;
try {
parsedUri = new URI(masterKeyPath);

// A valid URI.
// Check if it is pointing to a trusted endpoint.
String host = parsedUri.getHost();
if (null != host) {
host = host.toLowerCase(Locale.ENGLISH);
}
for (final String endpoint : azureTrustedEndpoints) {
if (null != host && host.endsWith(endpoint)) {
return;
}
}
} catch (URISyntaxException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVURLInvalid"));
Object[] msgArgs = {masterKeyPath};
throw new SQLServerException(form.format(msgArgs), null, 0, e);
}

// A valid URI.
// Check if it is pointing to a trusted endpoint.
for (final String endpoint : azureTrustedEndpoints) {
if (parsedUri.getHost().toLowerCase(Locale.ENGLISH).endsWith(endpoint)) {
return;
}
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_AKVMasterKeyPathInvalid"));
Object[] msgArgs = {masterKeyPath};
throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
Expand Down Expand Up @@ -590,4 +595,37 @@ private int getAKVKeySize(String masterKeyPath) throws SQLServerException {

return retrievedKey.key().n().length;
}

@Override
public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
byte[] signature) throws SQLServerException {
if (!allowEnclaveComputations)
return false;

KeyStoreProviderCommon.validateNonEmptyMasterKeyPath(masterKeyPath);

try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(name.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
md.update(masterKeyPath.toLowerCase().getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
// value of allowEnclaveComputations is always true here
md.update("true".getBytes(java.nio.charset.StandardCharsets.UTF_16LE));

byte[] dataToVerify = md.digest();
if (null == dataToVerify) {
throw new SQLServerException(SQLServerException.getErrString("R_HashNull"), null);
}

// Sign the hash
byte[] signedHash = AzureKeyVaultSignHashedData(dataToVerify, masterKeyPath);
if (null == signedHash) {
throw new SQLServerException(SQLServerException.getErrString("R_SignedHashLengthError"), null);
}

// Validate the signature
return AzureKeyVaultVerifySignature(dataToVerify, signature, masterKeyPath);
} catch (NoSuchAlgorithmException e) {
throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,15 @@ public byte[] decryptColumnEncryptionKey(String masterKeyPath, String encryption
"decryptColumnEncryptionKey", "Finished decrypting Column Encryption Key.");
return plainCek;
}

@Override
public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
byte[] signature) throws SQLServerException {
try {
return AuthenticationJNI.VerifyColumnMasterKeyMetadata(masterKeyPath, allowEnclaveComputations, signature);
} catch (DLLException e) {
DLLException.buildException(e.GetErrCode(), e.GetParam1(), e.GetParam2(), e.GetParam3());
return false;
}
}
}
Loading

0 comments on commit 276d2ca

Please sign in to comment.