Always Encrypted v2 support (#1045)

* Change to support ae-v2

* Add support for AE V2

* Added some descriptions and comments

* Fixed PDO pattern matching

* Updated key generation scripts

* Fixed key script

* Fixed char/nchar results, fixed formatting issues

* Addressed review comments

* Updated key scripts

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Added skipif to ae v2 keyword test

* Addressed review comments

* Fixed braces and camel caps

* Updated test descriptions

* Added detail to test descriptions

* Tiny change
This commit is contained in:
David Puglielli 2019-10-31 16:55:36 -07:00 committed by GitHub
parent aec733b764
commit 051328782d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2534 additions and 41 deletions

View file

@ -718,14 +718,14 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v
const char *pch = strchr(pstr, '}');
size_t i = 0;
while (pch != NULL && i < value_len) {
i = pch - pstr + 1;
if (i == value_len || (i < value_len && pstr[i] != '}')) {
return false;
}
i++; // skip the brace
pch = strchr(pch + 2, '}'); // continue searching
}
@ -783,7 +783,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
try {
// Since connection options access token and authentication cannot coexist, check if both of them are used.
// If access token is specified, check UID and PWD as well.
// If access token is specified, check UID and PWD as well.
// No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers
if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) {
bool invalidOptions = false;
@ -801,7 +801,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
access_token_used = true;
}
// Check if Authentication is ActiveDirectoryMSI
// Check if Authentication is ActiveDirectoryMSI
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false;
if (authentication_option_used) {
@ -813,7 +813,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = true;
// There are two types of managed identities:
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string
// In both cases, PWD must be NULL
@ -832,11 +832,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
}
}
}
// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC );
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// because they are incompatible
if (!access_token_used && !activeDirectoryMSI) {
if (uid == NULL || strnlen_s(uid) == 0) {
@ -1153,9 +1153,12 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
// Column Encryption is disabled by default, but if it is present and not
// explicitly set to disabled or enabled, the ODBC driver will assume the
// user is providing an attestation protocol and URL for enclave support.
// For our purposes we need only set ce_option.enabled to true if not disabled.
conn->ce_option.enabled = false;
if ( !stricmp(value_str, "enabled" )) {
if ( stricmp(value_str, "disabled" )) {
conn->ce_option.enabled = true;
}
@ -1200,7 +1203,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
char *pValue = static_cast<char*>(sqlsrv_malloc(value_len + 1));
memcpy_s(pValue, value_len + 1, value_str, value_len);
pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage
// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) {
conn->ce_option.akv_id = pValue;
@ -1262,10 +1265,10 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
}
const char* value_str = Z_STRVAL_P( value );
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
@ -1276,30 +1279,30 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token
size_t dataSize = 2 * value_len;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize));
ACCESSTOKEN *pAccToken = accToken.get();
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token.");
pAccToken->dataSize = dataSize;
// Expand access token with padding bytes
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) {
pAccToken->data[i] = value_str[j];
pAccToken->data[i+1] = 0;
}
core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER);
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn->azure_ad_access_token = pAccToken;
accToken.transferred();
}

View file

@ -0,0 +1,163 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// Names of the encryption keys, depending on whether we are using Windows
// or AKV authentication (defined in MsSetup.inc). -enclave keys are enclave
// enabled, -noenclave keys are not enclave enabled.
// $targetKeys are the keys used for re-encrypting encrypted columns
if ($keystore == 'win') {
$keys = array("CEK-win-enclave", "CEK-win-noenclave");
$targetKeys = array("CEK-win-enclave", "CEK-win-enclave2", "CEK-win-noenclave", "CEK-win-noenclave2");
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave", "CEK-akv-noenclave");
$targetKeys = array("CEK-akv-enclave", "CEK-akv-enclave2", "CEK-akv-noenclave", "CEK-akv-noenclave2");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Randomized", "Deterministic");
$targetTypes = array("Deterministic", "Randomized");
// Length of the string-type columns. $slength is length as a string instead of integer
$length = 64;
$slength = '64';
// Testing the following data types.
// TODO: Add binary string types and fix smalldatetime issues.
$dataTypes = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
//'binary',
//'varbinary',
//'varbinary(max)',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
//'smalldatetime',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames = array();
$colNamesAE = array();
foreach ($dataTypes as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames[$type] = "c_".$column;
$colNamesAE[$type] = "c_".$column."_AE";
}
// The test data below is a mixture of random data and edge cases
$testValues = array();
// integers
$testValues['integer'] = array(0,-1,1,2147483647,-2147483648,65536,-100000000,128,9);
$testValues['bigint'] = array(9223372036854775807,-40,0,1,2147483647,-2147483648,65536,-100000000000000);
$testValues['smallint'] = array(4,-4,-32768,-99,32767,-30000,-12,-1);
$testValues['tinyint'] = array(2,0,255,254,99,101,100,32);
$testValues['bit'] = array(1,1,0,0,0,0,1,0);
// floating point
$testValues['float'] = array(3.14159,2.3e+12,-2.3e+12,2.23e-308,1,-1.79e+308,892.3098234234001,1.2);
$testValues['real'] = array(3.14159,2.3e+12,-2.3e+12,1.18e-38,1,-3.4e+38,892.3098234234001,1.2);
$testValues['numeric'] = array(-3.14159,1.003456789,45.6789,-0.000000001,987987.12345,-987987.12345,100000000000,-100000000000);
// dates and times
$testValues['date'] = array('2010-01-31','0485-03-31','7825-07-23','9999-12-31','1956-02-27','2018-09-01','5401-11-02','1031-10-04');
$testValues['time'] = array('12:40:40','08:14:54.3096','23:59:59.9999','01:00:34.0101','21:45:45.4545','00:23:45.6','17:48:00.0000','20:31:49.0001');
$testValues['datetime2'] = array('9801-01-29 11:45:23.5092856','2384-12-31 12:40:12.5434323','1984-09-25 10:40:20.0909111','9999-12-31 23:59:59.999999',
'1259-04-29 23:59:59.9999999','1748-09-21 17:48:54.723','3125-05-31 05:00:32.4','0001-01-01 00:00:00');
$testValues['datetimeoffset'] = array('9801-01-29 11:45:23.5092856-12:45','0001-01-01 00:00:00-02:30','1984-09-25 10:40:20.0909111+03:00','1748-09-21 17:48:54.723-09:21',
'4896-05-18 23:17:58.3-02:00','1657-08-04 18:14:27.4','2022-03-17 07:31:45.890342+09:30','1987-10-25 14:27:34.6320945-06:00');
$testValues['datetime'] = array('9801-01-29 11:45:23.509','2384-12-31 12:40:12.543','1984-09-25 10:40:20.090','9999-12-31 23:59:59.997',
'2753-04-29 23:59:59.997','1948-09-21 17:48:54.723','3125-05-31 05:00:32.4','2001-01-01 00:00:00');
$testValues['smalldatetime'] = array('1998-06-13 04:00:30','1985-03-31 12:40:40','2025-07-23 05:00:32','1999-12-31 00:00:00',
'1956-02-27 23:59:59','2018-09-01 14:35:30','2079-06-06 23:59:29.997','1931-10-04 19:52:21');
// strings, ascii and unicode
$testValues['char'] = array('wvyxz', 'tposw', '%c@kj>5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@');
$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','ኁ㵮ഖᅥ㪮ኸ⮊ߒᙵꇕ⯐គꉟफ़⻦ꈔꇼŞ','ꐷꬕ','㐯㩧㖃⺵㴰ڇལᧆ겴ꕕ겑וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪぅᾔᎀ㍏겶ꅫၞ㴉ᴳ㜞҂','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬ㐯ꋛ㗾');
$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?');
$testValues['nvarchar'] = array('ᾁẴ㔮㖖ୱܝ㐗㴴៸ழ᷂ᵄ葉អ㺓節','ӕᏵ൴ꔓὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','ꁈ ݫ','ꍆફⷌ㏽̗ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮᏏᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲ更ꧥ');
$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z',
'9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nq<i-Nqbu@]1<K{PS[SHSF(]G[G XPLlAUezBm^&qn^mK(&]ss6&yVxW_N_J5V*iKcgXyb+Hz:HS<9>rc1%n@|N|ik C@ 03a/ +H9mBq','SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l<e`N@');
$testValues['nvarchar(max)'] = array('xᐕᛙᘡ','ퟅ㚶Ἢœäᑐï','ꐾɔᡧ㝚ஒŪᚔᘘښ곅սꕟքꀉᎠኇ','t9p4r5',
'﹨ퟱꈽᕧු꧍ۡᢙⴖ㒘ᆾ겇ᅞ〱㝸㛾㕥গଜ㳸ꍍ匿ཋ㵔ﬠᄩ᧙ꖍᕿ㩴ఽᙿ','ⴠ⿃ᶺ͚ᎉ઄㵨጑㛢㌋㙤ᙘّᘷ㬡',
'ᵄご︵ࣲꌤꏵퟰꖛᏠƢᵙꌵ㙈㜂琢㎯㪏㐵꒚㧶ᐁቴƯɋü㶌領㻡㉉걂ꈊㇷѼμꅲڧᶀƸڍ⭩㉩㛜ꆶ㕸ꁺꖁ㓫ޘ갧ᛄ㶋㘚ᐋꗡͭచ㖔፟ꐸ㱯ⵜᥰꃷᇂὥ㗍㚀ꀊጿἢઔܛ᎓Ե⅜㛵጑ྣᏝ༱⮢ΫÊ㕮⽹','繁Ɇʓӿꩭਸꆟꑇ㳋Ήᴝ㕨㰵ꇳ');
// binary
$testValues['binary'] = array(0x3AD2BBC2, 720, 0xEED3A109F8F7745C, 0xD6C3E0E11A25F,0x4EACCEF38788F9,0xFFFFFFFFFFFFFFFF,6230974598,0x44E4A);
$testValues['varbinary'] = array(58342,0xF3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC888,0xE4300FF,0x000005ED309D3A45,0x06CADE379,804,0x00000000000000,0xD7209FFE4);
$testValues['varbinary(max)'] = array(0xEF409CB33408, 0xD3EA762C78F,0,0xFFFFFFFFFFFFFFFFFFFFFFFFF,
0x582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1F,
0x38054,9230094389109,0xDDD4D88C4B80089D2E4A,0x88F8A8);
// The comparison operators to test
$comparisons = array('=', '<', '>', '<=', '>=', '<>', '!<', '!>');
// Thresholds against which to use the comparison operators
$thresholds = array('integer' => 0,
'bigint' => 0,
'smallint' => 1000,
'tinyint' => 100,
'bit' => 0,
'float' => 1.2,
'real' => -1.2,
'numeric' => 45.6789,
'char' => 'rstuv',
'nchar' => '㊃ᾞਲ㨴꧶ꁚꅍ',
'varchar' => '6$gt?je#~',
'nvarchar' => 'ӕᏵ൴ꔓὀ⾼',
'varchar(max)' => 'hijkl',
'nvarchar(max)' => 'xᐕᛙᘡ',
'binary' => 0x44E4A,
'varbinary' => 0xE4300FF,
'varbinary(max)' => 0xD3EA762C78F,
'date' => '2010-01-31',
'time' => '21:45:45.4545',
'datetime' => '3125-05-31 05:00:32.4',
'datetime2' => '2384-12-31 12:40:12.5434323',
'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00',
'smalldatetime' => '1998-06-13 04:00:30',
);
// String patterns to test with LIKE
$patterns = array('integer' => array('8', '48', '123'),
'bigint' => array('000','7', '65536'),
'smallint' => array('4','768','abc'),
'tinyint' => array('9','0','25'),
'bit' => array('0','1','100'),
'float' => array('14159','.','E+','2.3','308'),
'real' => array('30','.','e-','2.3','38'),
'numeric' => array('0','0000','12345','abc','.'),
'char' => array('w','@','x*fv=u$','e3'),
'nchar' => array('af㋮','㐯ꋛ㗾','ꦣ㧼ለͭ','123'),
'varchar' => array(' ','a','#','@@)'),
'nvarchar' => array('ӕ','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','更ꧥ','ꈔꇼŞ'),
'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'),
'nvarchar(max)' => array('t','㧶ᐁቴƯɋ','ᘷ㬡',' ','ꐾɔᡧ㝚'),
'binary' => array('0x44E4A'),
'varbinary' => array('0xE4300FF'),
'varbinary(max)' => array('0xD3EA762C78F'),
'date' => array('20','%','9-','04'),
'time' => array('4545','.0','20:','12345',':'),
'datetime' => array('997','12',':5','9999'),
'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'),
'datetimeoffset' => array('+02','96',' ','5092856',':00'),
'smalldatetime' => array('00','1999','abc',':','06'),
);
?>

View file

@ -49,4 +49,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret
// for enclave computations
$attestation = 'TARGET_ATTESTATION';
?>

View file

@ -0,0 +1,488 @@
<?php
// Connect and clear the procedure cache
function connect($server, $attestation_info)
{
include("MsSetup.inc");
$options = "sqlsrv:Server=$server;Database=$databaseName;ColumnEncryption=$attestation_info";
if ($keystore == 'akv') {
$security_info = '';
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$security_info .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$security_info .= ";KeyStorePrincipalId=$AKVPrincipalName";
$security_info .= ";KeyStoreSecret=$AKVPassword";
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$security_info .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$security_info .= ";KeyStorePrincipalId=$AKVClientID";
$security_info .= ";KeyStoreSecret=$AKVSecret";
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options .= $security_info;
}
$conn = new PDO($options, $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_DATETIME_TYPE, true);
// Check that enclave computations are enabled
// See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = $conn->query($query);
$info = $stmt->fetch();
if ($info['value'] != 1 or $info['value_in_use'] != 1) {
die("Error: enclave computations are not enabled on the server!");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->exec("DBCC FREEPROCCACHE");
return $conn;
}
// This CREATE TABLE query simply creates a non-encrypted table with
// two columns for each data type side by side
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer,
// c_integer_AE integer
// )
function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength."), \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n ";
} else {
$query = $query.$colNames[$type]." ".$type.", \n ";
$query = $query.$colNamesAE[$type]." ".$type.", \n ";
}
}
// Remove the ", \n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -7)."\n)";
return $query;
}
// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must
// be preceded by ALTER TABLE. This query can be used to both encrypt plaintext
// columns and to re-encrypt encrypted columns.
// This produces a query that looks like
// ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_integer_AE] integer
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_bigint_AE] bigint
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength)
{
$query = '';
foreach ($dataTypes as $dataType) {
$plength = dataTypeIsString($dataType) ? "(".$slength.")" : "";
$collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : "";
$query = $query." ALTER TABLE [dbo].[".$tableName."]
ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate."
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH
(ONLINE = ON);";
}
$query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;";
return $query;
}
// This CREATE TABLE query creates a table with two columns for
// each data type side by side, one plaintext and one encrypted
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer NULL,
// c_integer_AE integer
// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
// )
function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
$collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : "";
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
} else {
$query = $query.$colNames[$type]." ".$type." NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type." \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
}
}
// Remove the ",\n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -6)."\n)";
return $query;
}
// The INSERT query for the table
function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE)
{
$queryTypes = "(";
$valuesString = "VALUES (";
foreach ($dataTypes as $type) {
$colName1 = $colNames[$type].", ";
$colName2 = $colNamesAE[$type].", ";
$queryTypes .= $colName1;
$queryTypes .= $colName2;
$valuesString .= "?, ?, ";
}
// Remove the ", " from the end of the query or the comma will cause a syntax error
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
return $insertQuery;
}
function insertValues($conn, $insertQuery, $dataTypes, $testValues)
{
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
foreach ($dataTypes as $type) {
$insertValues[] = $testValues[$type][$v];
$insertValues[] = $testValues[$type][$v];
}
// Insert the data using PDO::prepare()
try {
$stmt = $conn->prepare($insertQuery);
$stmt->execute($insertValues);
} catch (PDOException $error) {
print_r($error);
die("Inserting values in encrypted table failed\n");
}
}
}
// compareResults checks that the results between the encrypted and non-encrypted
// columns are identical if statement execution succeeds. If statement execution
// fails, this function checks for the correct error.
// Arguments:
// statement $AEstmt: Prepared statement fetching encrypted data
// statement $nonAEstmt: Prepared statement fetching non-encrypted data
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// string $comparison: Comparison operator
// string $type: Data type the comparison is operating on
function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison='', $type='')
{
try {
$nonAEstmt->execute();
} catch(Exception $error) {
print_r($error);
die("Executing non-AE statement failed!\n");
}
try {
$AEstmt->execute();
} catch(Exception $error) {
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
} elseif (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} else {
print_r($error);
die("AE statement execution failed when it shouldn't!");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
$e = $error->errorInfo;
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
} elseif (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} else {
print_r($error);
die("AE statement execution failed when it shouldn't!");
}
} elseif ($attestation == 'correct') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} elseif ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} else {
print_r($error);
die("Comparison failed for correct attestation when it shouldn't have!\n");
}
} else {
print_r($error);
die("Unexpected error occurred in compareResults!\n");
}
return;
}
$AEres = $AEstmt->fetchAll(PDO::FETCH_NUM);
$nonAEres = $nonAEstmt->fetchAll(PDO::FETCH_NUM);
$AEcount = count($AEres);
$nonAEcount = count($nonAEres);
if ($type == 'char' or $type == 'nchar') {
// char and nchar may not return the same results - at this point
// we've verified that statement execution works so just return
// TODO: Check if this bug is fixed and if so, remove this if block
return;
} elseif ($AEcount > $nonAEcount) {
print_r("Too many AE results for operation $comparison and data type $type!\n");
print_r($AEres);
print_r($nonAEres);
} elseif ($AEcount < $nonAEcount) {
print_r("Too many non-AE results for operation $comparison and data type $type!\n");
print_r($AEres);
print_r($nonAEres);
} else {
if ($AEcount != 0) {
$i = 0;
foreach ($AEres as $AEr) {
if ($AEr[0] != $nonAEres[$i][0]) {
print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i][0]." and non-AE result ".$nonAEres[$i][0]."\n");
}
++$i;
}
}
}
}
// testCompare selects based on a comparison in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $comparisons: Comparison operations from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, $attestation)
{
foreach ($comparisons as $comparison) {
foreach ($dataTypes as $type) {
// Unicode operations with AE require the Latin1_General_BIN2
// collation. If the COLLATE clause is left out, we get different
// results between the encrypted and non-encrypted columns (probably
// because the collation was only changed in the encryption query).
$string = dataTypeIsStringMax($type);
$collate = $string ? " COLLATE Latin1_General_BIN2" : "";
$unicode = dataTypeIsUnicode($type);
$PDOType = getPDOType($type);
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate;
try {
$AEstmt = $conn->prepare($AEQuery);
$AEstmt->bindParam(1, $thresholds[$type], $PDOType);
$nonAEstmt = $conn->prepare($nonAEQuery);
$nonAEstmt->bindParam(1, $thresholds[$type], $PDOType);
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison, $type);
}
}
}
// testPatternMatch selects based on a pattern in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Table name
// array $patterns: Patterns to match against, from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types
// without an explicit cast
if (!dataTypeIsStringMax($type)) {
continue;
}
foreach ($patterns[$type] as $pattern) {
$patternArray = array($pattern,
$pattern."%",
"%".$pattern,
"%".$pattern."%",
);
foreach ($patternArray as $spattern) {
// Unicode operations with AE require the Latin1_General_BIN2
// collation. If the COLLATE clause is left out, we get different
// results between the encrypted and non-encrypted columns (probably
// because the collation was only changed in the encryption query).
$unicode = dataTypeIsUnicode($type);
$collate = $unicode ? " COLLATE Latin1_General_BIN2" : "";
$PDOType = getPDOType($type);
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate;
try {
$AEstmt = $conn->prepare($AEQuery);
$AEstmt->bindParam(1, $spattern, $PDOType);
$nonAEstmt = $conn->prepare($nonAEQuery);
$nonAEstmt->bindParam(1, $spattern, $PDOType);
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $pattern, $type);
}
}
}
}
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0] and $code[1]==$errors[1]) {
$codeFound = true;
break;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
die("Error code not found.\n");
}
}
function isEnclaveEnabled($key)
{
return (strpos($key, '-enclave') !== false);
}
function dataTypeIsString($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"]));
}
function dataTypeIsStringMax($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeNeedsCollate($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeIsUnicode($dataType)
{
return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"]));
}
function getPDOType($type)
{
switch($type) {
case "bigint":
case "integer":
case "smallint":
case "tinyint":
return PDO::PARAM_INT;
case "bit":
return PDO::PARAM_BOOL;
case "real":
case "float":
case "double":
case "numeric":
case "time":
case "date":
case "datetime2":
case "datetime":
case "datetimeoffset":
case "smalldatetime":
case "money":
case "smallmoney";
case "xml":
case "uniqueidentifier":
case "char":
case "varchar":
case "varchar(max)":
case "nchar":
case "nvarchar":
case "nvarchar(max)":
return PDO::PARAM_STR;
case "binary":
case "varbinary":
case "varbinary(max)":
return PDO::PARAM_LOB;
default:
die("Case is missing for $type type in getPDOType.\n");
}
}
?>

View file

@ -0,0 +1,93 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with ColumnEncryption set to 'enabled'.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with ColumnEncryption set to 'enabled'
$newAttestation = 'enabled';
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'enabled');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'enabled');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
// Query should fail and trigger catch block before getting here
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
} catch (PDOException $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
}
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,136 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating a plaintext table
each time, then trying to encrypt it with different combinations of enclave-enabled and non-enclave keys
and encryption types. It then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result
to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Re-encrypt the table using new key and/or encryption type.
7. Compare computations as in 4. above.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create and populate a non-encrypted table
$createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitDataTypes = array_chunk($dataTypes, 5);
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength);
$encryptionFailed = false;
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($key)) {
die("Encrypting should have failed with key $key and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if (!isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have!\n");
}
}
}
}
if ($encryptionFailed) continue;
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Try re-encrypting the table
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$encryptionFailed = false;
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
} catch (Exception $error) {
if (!isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have!\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,60 @@
--TEST--
Test various settings for the ColumnEncryption keyword.
--DESCRIPTION--
For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL].
If [protocol] is wrong, connection should fail; if the URL is wrong, connection
should succeed. This test sets ColumnEncryption to three values:
1. Random nonsense, which is interpreted as an incorrect protocol
so connection should fail.
2. Incorrect protocol with a correct attestation URL, connection should fail.
3. Correct protocol and incorrect URL, connection should succeed.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
// Test with random nonsense. Connection should fail.
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=xyz";
try {
$conn = new PDO($options, $uid, $pwd);
die("Connection should have failed!\n");
} catch(PDOException $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
}
// Test with incorrect protocol and good attestation URL. Connection should fail.
// Insert a rogue 'x' into the protocol part of the attestation.
$comma = strpos($attestation, ',');
$badProtocol = substr_replace($attestation, 'x', $comma, 0);
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badProtocol";
try {
$conn = new PDO($options, $uid, $pwd);
die("Connection should have failed!\n");
} catch(Exception $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
}
// Test with good protocol and incorrect attestation URL. Connection should succeed
// because the URL is only checked when an enclave computation is attempted.
$badURL = substr_replace($attestation, 'x', $comma+1, 0);
$options = "sqlsrv:Server=$server;database=$databaseName;ColumnEncryption=$badURL";
try {
$conn = new PDO($options, $uid, $pwd);
} catch(Exception $error) {
print_r($error);
die("Connecting with a bad attestation URL should have succeeded!\n");
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,109 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result
to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Re-encrypt the table using new key and/or encryption type.
6. Compare computations as in 4. above.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitDataTypes = array_chunk($dataTypes, 5);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r($error);
die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n");
}
continue;
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,95 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with a faulty attestation URL.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("pdo_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
try {
$stmt = $conn->query("DROP TABLE IF EXISTS $tableName");
$stmt = $conn->query($createQuery);
} catch(Exception $error) {
print_r($error);
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with a faulty attestation URL
$comma = strpos($attestation, ',');
$newAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $key, $encryptionType, 'wrongurl');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'wrongurl');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
try {
$stmt = $conn->query($alterQuery);
// Query should fail and trigger catch block before getting here
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
} catch(Exception $error) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
}
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,36 @@
<?php
// For AE v2, need ODBC driver 17.4 or above, an enclave enabled
// SQL Server, and a HGS server. The HGS server and SQL Server
// are the same for testing purposes.
if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
$connectionInfo = array("UID"=>$uid, "PWD"=>$pwd, "Driver" => $driver);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
}
$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsql_min = explode(".", $msodbcsql_ver)[1];
if ($msodbcsql_maj < 17) {
die("skip Unsupported ODBC driver version");
}
if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) {
die("skip Unsupported ODBC driver version");
}
// Get SQL Server
$server_info = sqlsrv_server_info($conn);
if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) {
die("skip Server is not HGS enabled");
}
?>

Binary file not shown.

View file

@ -1,35 +1,98 @@
/* DROP Column Encryption Key first, Column Master Key cannot be dropped until no encryption depends on it */
IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%')
/* DROP Column Encryption Keys first, Column Master Keys cannot be dropped until no CEKs depend on them */
IF EXISTS (SELECT * FROM sys.column_encryption_keys WHERE [name] LIKE '%AEColumnKey%' OR [name] LIKE '%-win-%')
BEGIN
DROP COLUMN ENCRYPTION KEY [AEColumnKey]
DROP COLUMN ENCRYPTION KEY [CEK-win-enclave]
DROP COLUMN ENCRYPTION KEY [CEK-win-enclave2]
DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave]
DROP COLUMN ENCRYPTION KEY [CEK-win-noenclave2]
END
GO
/* Can finally drop Column Master Key after the Encryption Key is dropped */
IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%')
/* Can finally drop Column Master Keys after the Column Encryption Keys are dropped */
IF EXISTS (SELECT * FROM sys.column_master_keys WHERE [name] LIKE '%AEMasterKey%' OR [name] LIKE '%-win-%')
BEGIN
DROP COLUMN MASTER KEY [AEMasterKey]
DROP COLUMN MASTER KEY [CMK-win-enclave]
DROP COLUMN MASTER KEY [CMK-win-noenclave]
END
GO
/* Recreate the Column Master Key */
/* Create the Column Master Keys */
/* AKVMasterKey is a non-enclave enabled key for AE v1 testing */
/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */
CREATE COLUMN MASTER KEY [AEMasterKey]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816'
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/my/237F94738E7F5214D8588006C2269DBC6B370816'
)
GO
/* Create Column Encryption Key using the Column Master Key */
/* The enclave-enabled master key requires an ENCLAVE_COMPUTATIONS clause */
CREATE COLUMN MASTER KEY [CMK-win-enclave]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854',
ENCLAVE_COMPUTATIONS (SIGNATURE = 0xA1150DE565E9C132D2AAB8FF8B228EAA8DA804F250B5B422874CB608A3B274DDE523E71B655A3EFC6C3018B632701E9205BAD80C178614E1FE821C6807B0E70BCF11168FC4B202638905C5F016EDBADACA23C696B79772C56825F36EB8C0366B130C91D85362E560C9D2FDD20DCAE99619256045CA2725DEC9E0C115CAEB9EA686CCB0DE0D53D2056C01752B17B634FC6DBB51EA043F607349489722DB8A086CBC876649284A8352822DD22B328E7BA3D671CCDF54CDAAF61DFD6AF2EAAC14E03897324234AB103C45AB48131C1CD19040782359FC920A0AF61BA9842ADFB76C3196CBC6EB9C0A679926ED63E092B7C8643232C97A64C7F918104C210787A56F)
)
GO
CREATE COLUMN MASTER KEY [CMK-win-noenclave]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/D9C0572FA54B221D6591C473BAEA53FE61AAC854'
)
GO
/* Now we can create the Column Encryption Keys */
/* ENCRYPTED_VALUE is generated by SSMS and it is always the same if the same Certificate is imported */
CREATE COLUMN ENCRYPTION KEY [AEColumnKey]
WITH VALUES
(
COLUMN_MASTER_KEY = [AEMasterKey],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E
COLUMN_MASTER_KEY = [AEMasterKey],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00320033003700660039003400370033003800650037006600350032003100340064003800350038003800300030003600630032003200360039006400620063003600620033003700300038003100360039DE2397A08F6313E7820D75382D8469BE1C8F3CD47E3240A5A6D6F82D322F6EB1B103C9C47999A69FFB164D37E7891F60FFDB04ADEADEB990BE88AE488CAFB8774442DF909D2EF8BB5961A5C11B85BA7903E0E453B27B49CE0A30D14FF4F412B5737850A4C564B44C744E690E78FAECF007F9005E3E0FB4F8D6C13B016A6393B84BB3F83FEED397C4E003FF8C5BBDDC1F6156349A8B40EDC26398C9A03920DD81B9197BC83A7378F79ECB430A04B4CFDF3878B0219BB629F5B5BF3C2359A7498AD9A6F5D63EF15E060CDB10A65E6BF059C7A32237F0D9E00C8AC632CCDD68230774477D4F2E411A0E4D9B351E8BAA87793E64456370D91D4420B5FD9A252F6D9178AE3DD02E1ED57B7F7008114272419F505CBCEB109715A6C4331DEEB73653990A7140D7F83089B445C59E4858809D139658DC8B2781CB27A749F1CE349DC43238E1FBEAE0155BF2DBFEF6AFD9FD2BD1D14CEF9AC125523FD1120488F24416679A6041184A2719B0FC32B6C393FF64D353A3FA9BC4FA23DFDD999B0771A547B561D72B92A0B2BB8B266BC25191F2A0E2F8D93648F8750308DCD79BE55A2F8D5FBE9285265BEA66173CD5F5F21C22CC933AE2147F46D22BFF329F6A712B3D19A6488DDEB6FDAA5B136B29ADB0BA6B6D1FD6FBA5D6A14F76491CB000FEE4769D5B268A3BF50EA3FBA713040944558EDE99D38A5828E07B05236A4475DA27915E
)
GO
GO
/* There are two enclave enabled keys and two non-enclave enabled keys to test the case where a user
tries to reencrypt a table from one enclave enabled key to another enclave enabled key, or from a
non-enclave key to another non-enclave key */
CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-enclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034007382EDDDE3FFCE076D5715B6BBBD22EA64E665899BEFAAD5B329F218EE30BE9F789EB98717B6FD9E50AE496AC9FEED962B23442D4FD3FBFEC9C9B65F40A3BCEC7CFAC198F4CAEE8A255F67988289EF050F9F75D0287F3DF9A9FDA0C674E48DF2CB13298AAAD039930DD909EEE71682CC8A90202D3F2A1F1037BB20B1954C8B6A11F05D104CA9DAF1561C6B2F9DBB08BCE17244157B751C02FC1730E387F372C31327F2834D19AF626D0B46B152615F05FA2F3566350312CDE6DE1160B3C1D0FD35FAF13891C04711DF184DA501AA51D16BF009EA71A2D28E201804C6F8F9100E90234923B2713EA7988861FBA4E292E5518FFC02CCBD2513EDA871F6E03ECDDD309619557277C10A07906E55BA3F59A6A18834B4CD5185DA4B4574A18B8B1AC53A2C36B033D7A72443F1438E76E37306A1F92AC30BC751F6D7ED1633FEE807440E1D6096C53C5E3E33828C9C59E8761E5BAD341C6D9E2BD1F2B5C3992666620CAA38C4645C154976EF62AE80161A9F7700C96875A72995E1C585918B28F65060F1B8B96417328F6DEDFCA79ED9F01EAB19FF4E3163F9963BA26E9B58031A04320CC73702A6ED438513E0F8ABA1966B53114038CC587050F90D9CD0F9E26CA9749723ABA85CF31F963A5E85E04993B2B2869725E734BE8FCFD30A801825582730B49C00A2058C02D3312D6D8E82078FF4F77C5FF9CE6E9D140F1A4517635AB784
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-enclave2]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-enclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034006B4D40ABF0975AF7C5CA7D1F4345DE437318556F5A2380DCFE4AB792DC3A424EABC80EA24EE850FACD94F04809C8B32674C6FF2D966FA7F9F9E522990E5F5011515BA4B7EF3603619D8A4BF46AA9B769A8A4417462C4B0303F995F04964A2E328A503D87CD1AB85ECFCB8241D0C815540989DC33E58EDCCBAFF0753E196813E3FCCC5A3C9E4277DD528AE276F1F795973A4DF8D1BB3B1F405B5F35A6A583F0BB86BAD7FCADC1FCF6B14B602890109360FAB67D6A27DE542AE87784C40FEB9071AC34C4C40C92A6C153A4A38B6DA3AD48ED39E32D6D161ACE7EFE516B414139A831D878C13FF178649823C4EFDC8E5DB4C02F2147CC76965C01C2F3624EB809FD4F5C2E291056077B1ABEFF1F5001C1F4248704C7C70CF63DA1EBC2FEC4A3DF919BA4F6B465819BC4587599C2E7499CDE62D7C335CE7BBCFC72242A8F41C1B5C94DEB0A9AF49B723759A8CD9751EE70DDEBAFA1957382287F621790543841EBCCA0007BA030CAF29E9FBF8CEB4FEC88673F47B5EC3B5F759BBDD8ED2EAF572711D78286E4294B89FF6EBFEE4968B4596AF3B5C34985F28E886F6C211F385326F10ED62602007589FC494372902FB32B0E3D67A8C64F43A87B06EE9F2CF074EB6F3EC7A431733EDA8745051B7A4AA4C020797A9492E6A3BA643D031E491497BF17539993871085AC249D0AD82203CD442F69D6C686D26F4D17BA46B69D3CB7E395
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-noenclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F00640039006300300035003700320066006100350034006200320032003100640036003500390031006300340037003300620061006500610035003300660065003600310061006100630038003500340042DC7A3AAAD184E01288C0913EFB6FEC6167CD8EA08A5F46ADCCC34D3AA6A1BDDDA15EA3DD219ED8795AB05C0111E48EA35A82ADDF2A206FACBBF4FD73D01C004DF627012D3950FEBCA4BBEDBDF97BA77033728D8873BA81E1C7BDCBE04BB3AA7EB42A1EDDBEF9B1CA9477ADA33F76711FEDF782CA1BD3C0104FDEB9E0D66DFCEC7D3C236906481B44F04457549658635322447742FB00B6D6F36A7CFCC56BB39F7280736BC25FD499F9CBA2F63CE11D53E536FD4A266929E06CF2BDBAF229894A77EDE140323B674ECF28C58C3E0B6C2E9407AD1A26776CB55D68B8286F64787CE5A468CFA27295D6069EFA5D65CD9A04602E861F4504F2611AAE6A8ADE33038A2BECE8BD7CF5B48567C217E324F11935C552FD25FE1FEFB152684BD1B3F8EB70EC9F6439340CE82CD8E74DD5986A6C4F9E8336ED4AC804FAD800A3EA324F78DCE37832035C3DC92782A06150916D01322A80767D1A36D7A8D9BCF6727DCE6AC67A168FA8B8B5032E60DCB178B21A860F2D98BE09DA9BA5DCCBD0D339369FF3C50C7993463372CF5B1DA9FAA12CD16E76F5961C01EADC5804C7F22227E2095BAD0F90A47B6330B1B43407E01DE5B61CEBD542A93797428AD84376E9362EADE6DDD103B9EC96E616A2ECED7D1D665B5B872E77FC024AD92AB4A8335D12D41BDD152790E87590798C1005956F9F92D4DD0C1C9852D147F7CB55B3224DE8EF593F
)
GO
CREATE COLUMN ENCRYPTION KEY [CEK-win-noenclave2]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK-win-noenclave],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x016E000001630075007200720065006E00740075007300650072002F006D0079002F0064003900630030003500370032006600610035003400620032003200310064003600350039003100630034003700330062006100650061003500330066006500360031006100610063003800350034009014CD16FC878CEA2DE91C8C681AE86C7C062D8BD88C4CEE501A89FEAC47356D7181644A350F72B5F6023DA2B9E26C5A2522C08B1910D390068CF26794F4BA7B0298A6676B4DC6DED913E3B077B56224D2E1A3FE4EF33F58FE44CFC3DD67E54FB15BE8E29ABAF8357F378FBEDA3EBF9868A54746074D5E0E798047867E1ABD39AD0645BB8E071C72BFC37C007CBFC58F5690A5253F444E77169B2FE92FD95897A412B2078DA3804A00723D6DF824FCA527208A1DFB377B5BA16B620213F8252E10E7D7A3719A3FBB2F7A8189792B0BCF737236963C7DDCA6366F7B04F127925A1F8DDBB1B5A01D280BD300ECA3B1F31F24C8A0D517AE7BCBC3233A24E83B70A334754098DE373A1C027A4D09BB1D26C930E7501EB02464C519D19CFA0B296238AF11638C2E0688C7599E3DB1714AACF4EBFCEF63E1EE521A8E38E3BEFD4EF4991A15E8DD5CFD94E58E68754F3E90BC117025C01562F6440417A42612BE9C8871A18108CBE3E96DA7E35C45171C03E1DFBB3CA1E35A6D322F2D5B79E2BF2A07F14136DA4A768E08E2A7F1A42E04B717CB6AE3D1A3FA0EACCFC9CEC27DB53761E13DE1F55B410A65FB441D50CF8B2153B64925B1CEBDE062B5CAF4C99C41FED6836327037C46515710F16DC611305A0EBA1943A9BA5CC6889626990879713E9C95BB54D6A8A3C1C05A10AFE142B2487A1F0A07B57841E940CC9816E3F43CAE3CB7
)
GO

View file

@ -31,6 +31,8 @@ def setupAE(conn_options, dbname):
# import self signed certificate
inst_command = "certutil -user -p '' -importPFX My PHPcert.pfx NoRoot"
executeCommmand(inst_command)
inst_command = "certutil -user -p '' -importPFX My AEV2Cert.pfx NoRoot"
executeCommmand(inst_command)
# create Column Master Key and Column Encryption Key
script_command = 'sqlcmd -I ' + conn_options + ' -i ae_keys.sql -d ' + dbname
executeCommmand(script_command)

View file

@ -0,0 +1,163 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// Names of the encryption keys, depending on whether we are using Windows
// or AKV authentication (defined in MsSetup.inc). -enclave keys are enclave
// enabled, -noenclave keys are not enclave enabled.
// $targetKeys are the keys used for re-encrypting encrypted columns
if ($keystore == 'win') {
$keys = array("CEK-win-enclave", "CEK-win-noenclave");
$targetKeys = array("CEK-win-enclave", "CEK-win-enclave2", "CEK-win-noenclave", "CEK-win-noenclave2");
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave", "CEK-akv-noenclave");
$targetKeys = array("CEK-akv-enclave", "CEK-akv-enclave2", "CEK-akv-noenclave", "CEK-akv-noenclave2");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Randomized", "Deterministic");
$targetTypes = array("Deterministic", "Randomized");
// Length of the string-type columns. $slength is length as a string instead of integer
$length = 64;
$slength = '64';
// Testing the following data types.
// TODO: Add binary string types and fix smalldatetime issues.
$dataTypes = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
//'binary',
//'varbinary',
//'varbinary(max)',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
//'smalldatetime',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames = array();
$colNamesAE = array();
foreach ($dataTypes as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames[$type] = "c_".$column;
$colNamesAE[$type] = "c_".$column."_AE";
}
// The test data below is a mixture of random data and edge cases
$testValues = array();
// integers
$testValues['integer'] = array(0,-1,1,2147483647,-2147483648,65536,-100000000,128,9);
$testValues['bigint'] = array(9223372036854775807,-40,0,1,2147483647,-2147483648,65536,-100000000000000);
$testValues['smallint'] = array(4,-4,-32768,-99,32767,-30000,-12,-1);
$testValues['tinyint'] = array(2,0,255,254,99,101,100,32);
$testValues['bit'] = array(1,1,0,0,0,0,1,0);
// floating point
$testValues['float'] = array(3.14159,2.3e+12,-2.3e+12,2.23e-308,1,-1.79e+308,892.3098234234001,1.2);
$testValues['real'] = array(3.14159,2.3e+12,-2.3e+12,1.18e-38,1,-3.4e+38,892.3098234234001,1.2);
$testValues['numeric'] = array(-3.14159,1.003456789,45.6789,-0.000000001,987987.12345,-987987.12345,100000000000,-100000000000);
// dates and times
$testValues['date'] = array('2010-01-31','0485-03-31','7825-07-23','9999-12-31','1956-02-27','2018-09-01','5401-11-02','1031-10-04');
$testValues['time'] = array('12:40:40','08:14:54.3096','23:59:59.9999','01:00:34.0101','21:45:45.4545','00:23:45.6','17:48:00.0000','20:31:49.0001');
$testValues['datetime2'] = array('9801-01-29 11:45:23.5092856','2384-12-31 12:40:12.5434323','1984-09-25 10:40:20.0909111','9999-12-31 23:59:59.999999',
'1259-04-29 23:59:59.9999999','1748-09-21 17:48:54.723','3125-05-31 05:00:32.4','0001-01-01 00:00:00');
$testValues['datetimeoffset'] = array('9801-01-29 11:45:23.5092856-12:45','0001-01-01 00:00:00-02:30','1984-09-25 10:40:20.0909111+03:00','1748-09-21 17:48:54.723-09:21',
'4896-05-18 23:17:58.3-02:00','1657-08-04 18:14:27.4','2022-03-17 07:31:45.890342+09:30','1987-10-25 14:27:34.6320945-06:00');
$testValues['datetime'] = array('9801-01-29 11:45:23.509','2384-12-31 12:40:12.543','1984-09-25 10:40:20.090','9999-12-31 23:59:59.997',
'2753-04-29 23:59:59.997','1948-09-21 17:48:54.723','3125-05-31 05:00:32.4','2001-01-01 00:00:00');
$testValues['smalldatetime'] = array('1998-06-13 04:00:30','1985-03-31 12:40:40','2025-07-23 05:00:32','1999-12-31 00:00:00',
'1956-02-27 23:59:59','2018-09-01 14:35:30','2079-06-06 23:59:29.997','1931-10-04 19:52:21');
// strings, ascii and unicode
$testValues['char'] = array('wvyxz', 'tposw', '%c@kj>5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@');
$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','ኁ㵮ഖᅥ㪮ኸ⮊ߒᙵꇕ⯐គꉟफ़⻦ꈔꇼŞ','ꐷꬕ','㐯㩧㖃⺵㴰ڇལᧆ겴ꕕ겑וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪぅᾔᎀ㍏겶ꅫၞ㴉ᴳ㜞҂','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬ㐯ꋛ㗾');
$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?');
$testValues['nvarchar'] = array('ᾁẴ㔮㖖ୱܝ㐗㴴៸ழ᷂ᵄ葉អ㺓節','ӕᏵ൴ꔓὀ⾼','Ὡ','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','ꁈ ݫ','ꍆફⷌ㏽̗ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮᏏᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲ更ꧥ');
$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z',
'9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nq<i-Nqbu@]1<K{PS[SHSF(]G[G XPLlAUezBm^&qn^mK(&]ss6&yVxW_N_J5V*iKcgXyb+Hz:HS<9>rc1%n@|N|ik C@ 03a/ +H9mBq','SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l<e`N@');
$testValues['nvarchar(max)'] = array('xᐕᛙᘡ','ퟅ㚶Ἢœäᑐï','ꐾɔᡧ㝚ஒŪᚔᘘښ곅սꕟքꀉᎠኇ','t9p4r5',
'﹨ퟱꈽᕧු꧍ۡᢙⴖ㒘ᆾ겇ᅞ〱㝸㛾㕥গଜ㳸ꍍ匿ཋ㵔ﬠᄩ᧙ꖍᕿ㩴ఽᙿ','ⴠ⿃ᶺ͚ᎉ઄㵨጑㛢㌋㙤ᙘّᘷ㬡',
'ᵄご︵ࣲꌤꏵퟰꖛᏠƢᵙꌵ㙈㜂琢㎯㪏㐵꒚㧶ᐁቴƯɋü㶌領㻡㉉걂ꈊㇷѼμꅲڧᶀƸڍ⭩㉩㛜ꆶ㕸ꁺꖁ㓫ޘ갧ᛄ㶋㘚ᐋꗡͭచ㖔፟ꐸ㱯ⵜᥰꃷᇂὥ㗍㚀ꀊጿἢઔܛ᎓Ե⅜㛵጑ྣᏝ༱⮢ΫÊ㕮⽹','繁Ɇʓӿꩭਸꆟꑇ㳋Ήᴝ㕨㰵ꇳ');
// binary
$testValues['binary'] = array(0x3AD2BBC2, 720, 0xEED3A109F8F7745C, 0xD6C3E0E11A25F,0x4EACCEF38788F9,0xFFFFFFFFFFFFFFFF,6230974598,0x44E4A);
$testValues['varbinary'] = array(58342,0xF3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC888,0xE4300FF,0x000005ED309D3A45,0x06CADE379,804,0x00000000000000,0xD7209FFE4);
$testValues['varbinary(max)'] = array(0xEF409CB33408, 0xD3EA762C78F,0,0xFFFFFFFFFFFFFFFFFFFFFFFFF,
0x582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1F,
0x38054,9230094389109,0xDDD4D88C4B80089D2E4A,0x88F8A8);
// The comparison operators to test
$comparisons = array('=', '<', '>', '<=', '>=', '<>', '!<', '!>');
// Thresholds against which to use the comparison operators
$thresholds = array('integer' => 0,
'bigint' => 0,
'smallint' => 1000,
'tinyint' => 100,
'bit' => 0,
'float' => 1.2,
'real' => -1.2,
'numeric' => 45.6789,
'char' => 'rstuv',
'nchar' => '㊃ᾞਲ㨴꧶ꁚꅍ',
'varchar' => '6$gt?je#~',
'nvarchar' => 'ӕᏵ൴ꔓὀ⾼',
'varchar(max)' => 'hijkl',
'nvarchar(max)' => 'xᐕᛙᘡ',
'binary' => 0x44E4A,
'varbinary' => 0xE4300FF,
'varbinary(max)' => 0xD3EA762C78F,
'date' => '2010-01-31',
'time' => '21:45:45.4545',
'datetime' => '3125-05-31 05:00:32.4',
'datetime2' => '2384-12-31 12:40:12.5434323',
'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00',
'smalldatetime' => '1998-06-13 04:00:30',
);
// String patterns to test with LIKE
$patterns = array('integer' => array('8', '48', '123'),
'bigint' => array('000','7', '65536'),
'smallint' => array('4','768','abc'),
'tinyint' => array('9','0','25'),
'bit' => array('0','1','100'),
'float' => array('14159','.','E+','2.3','308'),
'real' => array('30','.','e-','2.3','38'),
'numeric' => array('0','0000','12345','abc','.'),
'char' => array('w','@','x*fv=u$','e3'),
'nchar' => array('af㋮','㐯ꋛ㗾','ꦣ㧼ለͭ','123'),
'varchar' => array(' ','a','#','@@)'),
'nvarchar' => array('ӕ','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','更ꧥ','ꈔꇼŞ'),
'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'),
'nvarchar(max)' => array('t','㧶ᐁቴƯɋ','ᘷ㬡',' ','ꐾɔᡧ㝚'),
'binary' => array('0x44E4A'),
'varbinary' => array('0xE4300FF'),
'varbinary(max)' => array('0xD3EA762C78F'),
'date' => array('20','%','9-','04'),
'time' => array('4545','.0','20:','12345',':'),
'datetime' => array('997','12',':5','9999'),
'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'),
'datetimeoffset' => array('+02','96',' ','5092856',':00'),
'smalldatetime' => array('00','1999','abc',':','06'),
);
?>

View file

@ -53,4 +53,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret
// for enclave computations
$attestation = 'TARGET_ATTESTATION';
?>

View file

@ -0,0 +1,36 @@
<?php
// For AE v2, need ODBC driver 17.4 or above, an enclave enabled
// SQL Server, and a HGS server. The HGS server and SQL Server
// are the same for testing purposes.
if (!extension_loaded("sqlsrv")) {
die("skip Extension not loaded");
}
require_once("MsSetup.inc");
$connectionInfo = array("UID"=>$userName, "PWD"=>$userPassword, "Driver" => $driver);
$conn = sqlsrv_connect( $server, $connectionInfo );
if ($conn === false) {
die( "skip Could not connect during SKIPIF." );
}
$msodbcsql_ver = sqlsrv_client_info($conn)["DriverVer"];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
$msodbcsql_min = explode(".", $msodbcsql_ver)[1];
if ($msodbcsql_maj < 17) {
die("skip Unsupported ODBC driver version");
}
if ($msodbcsql_min < 4 and $msodbcsql_maj == 17) {
die("skip Unsupported ODBC driver version");
}
// Get SQL Server
$server_info = sqlsrv_server_info($conn);
if (strpos($server_info['SQLServerName'], 'PHPHGS') === false) {
die("skip Server is not HGS enabled");
}
?>

View file

@ -0,0 +1,518 @@
<?php
// Connect and clear the procedure cache
function connect($server, $attestation_info)
{
include("MsSetup.inc");
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'CharacterSet'=>'UTF-8',
'ColumnEncryption'=>$attestation_info,
);
if ($keystore == 'akv') {
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVPrincipalName,
'KeyStoreSecret'=>$AKVPassword,
);
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$security_info = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVClientID,
'KeyStoreSecret'=>$AKVSecret,
);
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options = array_merge($options, $security_info);
}
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
echo "Connection failed\n";
print_r(sqlsrv_errors());
}
// Check that enclave computations are enabled
// See https://docs.microsoft.com/en-us/sql/relational-databases/security/encryption/configure-always-encrypted-enclaves?view=sqlallproducts-allversions#configure-a-secure-enclave
$query = "SELECT [name], [value], [value_in_use] FROM sys.configurations WHERE [name] = 'column encryption enclave type';";
$stmt = sqlsrv_query($conn, $query);
$info = sqlsrv_fetch_array($stmt);
if ($info['value'] != 1 or $info['value_in_use'] != 1) {
die("Error: enclave computations are not enabled on the server!");
}
// Enable rich computations
sqlsrv_query($conn, "DBCC traceon(127,-1);");
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
return $conn;
}
// This CREATE TABLE query simply creates a non-encrypted table with
// two columns for each data type side by side
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer,
// c_integer_AE integer
// )
function constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength."), \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength."), \n ";
} else {
$query = $query.$colNames[$type]." ".$type.", \n ";
$query = $query.$colNamesAE[$type]." ".$type.", \n ";
}
}
// Remove the ", \n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -7)."\n)";
return $query;
}
// The ALTER TABLE query encrypts columns. Each ALTER COLUMN directive must
// be preceded by ALTER TABLE
// This produces a query that looks like
// ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_integer_AE] integer
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER TABLE [dbo].[aev2test2]
// ALTER COLUMN [c_bigint_AE] bigint
// ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
// WITH
// (ONLINE = ON); ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
function constructAlterQuery($tableName, $colNames, $dataTypes, $key, $encryptionType, $slength)
{
$query = '';
foreach ($dataTypes as $dataType) {
$plength = dataTypeIsString($dataType) ? "(".$slength.")" : "";
$collate = dataTypeNeedsCollate($dataType) ? " COLLATE Latin1_General_BIN2" : "";
$query = $query." ALTER TABLE [dbo].[".$tableName."]
ALTER COLUMN [".$colNames[$dataType]."] ".$dataType.$plength." ".$collate."
ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NOT NULL
WITH
(ONLINE = ON);";
}
$query = $query." ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;";
return $query;
}
// This CREATE TABLE query creates a table with two columns for
// each data type side by side, one plaintext and one encrypted
// This produces a query that looks like
// CREATE TABLE aev2test2 (
// c_integer integer NULL,
// c_integer_AE integer
// COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK-win-enclave], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
// )
function constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType)
{
$query = "CREATE TABLE ".$tableName." (\n ";
foreach ($dataTypes as $type) {
$collate = dataTypeNeedsCollate($type) ? " COLLATE Latin1_General_BIN2" : "";
if (dataTypeIsString($type)) {
$query = $query.$colNames[$type]." ".$type."(".$slength.") NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type."(".$slength.") \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
} else {
$query = $query.$colNames[$type]." ".$type." NULL, \n ";
$query = $query.$colNamesAE[$type]." ".$type." \n ";
$query = $query." ".$collate." ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [".$key."], ENCRYPTION_TYPE = ".$encryptionType.", ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL,\n ";
}
}
// Remove the ",\n " from the end of the query or the comma will cause a syntax error
$query = substr($query, 0, -6)."\n)";
return $query;
}
// The INSERT query for the table
function constructInsertQuery($tableName, &$dataTypes, &$colNames, &$colNamesAE)
{
$queryTypes = "(";
$valuesString = "VALUES (";
foreach ($dataTypes as $type) {
$colName1 = $colNames[$type].", ";
$colName2 = $colNamesAE[$type].", ";
$queryTypes .= $colName1;
$queryTypes .= $colName2;
$valuesString .= "?, ?, ";
}
// Remove the ", " from the end of the query or the comma will cause a syntax error
$queryTypes = substr($queryTypes, 0, -2).")";
$valuesString = substr($valuesString, 0, -2).")";
$insertQuery = "INSERT INTO $tableName ".$queryTypes." ".$valuesString;
return $insertQuery;
}
function insertValues($conn, $insertQuery, $dataTypes, $testValues)
{
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// two copies of each value for the two columns for each data type
foreach ($dataTypes as $type) {
$insertValues[] = $testValues[$type][$v];
$insertValues[] = $testValues[$type][$v];
}
// Insert the data using sqlsrv_prepare()
$stmt = sqlsrv_prepare($conn, $insertQuery, $insertValues);
if ($stmt == false) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at prepare\n");
}
if (sqlsrv_execute($stmt) == false) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at execute\n");
}
}
}
// compareResults checks that the results between the encrypted and non-encrypted
// columns are identical if statement execution succeeds. If statement execution
// fails, this function checks for the correct error.
// Arguments:
// statement $AEstmt: Prepared statement fetching encrypted data
// statement $nonAEstmt: Prepared statement fetching non-encrypted data
// string $key: Name of the encryption key
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
// string $comparison: Comparison operator
// string $type: Data type the comparison is operating on
function compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison='', $type='')
{
if (!sqlsrv_execute($nonAEstmt)) {
print_r(sqlsrv_errors());
die("Executing non-AE statement failed!\n");
}
if(!sqlsrv_execute($AEstmt)) {
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
} elseif (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
} elseif (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} elseif ($attestation == 'correct') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} elseif ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption!\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} else {
print_r(sqlsrv_errors());
die("Comparison failed for correct attestation when it shouldn't have!\n");
}
} else {
print_r(sqlsrv_errors());
die("Unexpected error occurred in compareResults!\n");
}
} else {
// char and nchar may not return the same results - at this point
// we've verified that statement execution works so just return
// TODO: Check if this bug is fixed and if so, remove this if block
if ($type == 'char' or $type == 'nchar') {
return;
}
while($AEres = sqlsrv_fetch_array($AEstmt, SQLSRV_FETCH_NUMERIC)) {
$nonAEres = sqlsrv_fetch_array($nonAEstmt, SQLSRV_FETCH_NUMERIC);
if (!$nonAEres) {
print_r($AEres);
print_r(sqlsrv_errors());
print_r("Too many AE results for operation $comparison and data type $type!\n");
} else {
$i = 0;
foreach ($AEres as $AEr) {
if ($AEr != $nonAEres[$i]) {
print_r("AE and non-AE results are different for operation $comparison and data type $type! For field $i, got AE result ".$AEres[$i]." and non-AE result ".$nonAEres[$i]."\n");
print_r(sqlsrv_errors());
}
++$i;
}
}
}
if ($rr = sqlsrv_fetch_array($nonAEstmt)) {
print_r($rr);
print_r(sqlsrv_errors());
print_r("Too many non-AE results for operation $comparison and data type $type!\n");
}
}
}
// testCompare selects based on a comparison in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
// Arguments:
// resource $conn: The connection
// string $tableName: Thable name
// array $comparisons: Comparison operations from AE_v2_values.inc
// array $dataTypes: Data types from AE_v2_values.inc
// array $colNames: Column names
// array $thresholds: Values to use comparison operators against, from AE_v2_values.inc
// string $key: Name of the encryption key
// integer $length: Length of the string types, from AE_v2_values.inc
// string $encryptionType: Type of encryption, randomized or deterministic
// string $attestation: Type of attestation - 'correct', 'enabled', or 'wrongurl'
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation)
{
foreach ($comparisons as $comparison) {
foreach ($dataTypes as $type) {
// Unicode operations with AE require the PHPTYPE to be specified to
// UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE
// clause is left out, we get different results between the
// encrypted and non-encrypted columns (probably because the
// collation was only changed in the encryption query).
$string = dataTypeIsStringMax($type);
$unicode = dataTypeIsUnicode($type);
$collate = $string ? " COLLATE Latin1_General_BIN2" : "";
$phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null;
$param = array(array($thresholds[$type], SQLSRV_PARAM_IN, $phptype, getSQLType($type, $length)));
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE ".$comparison." ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." ".$comparison." ?".$collate;
$AEstmt = sqlsrv_prepare($conn, $AEQuery, $param);
if (!$AEstmt) {
print_r(sqlsrv_errors());
die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n");
}
$nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param);
if (!$nonAEstmt) {
print_r(sqlsrv_errors());
die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $comparison, $type);
}
}
}
// testPatternMatch selects based on a pattern in the WHERE clause and compares
// the results between encrypted and non-encrypted columns, checking that the
// results are identical
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation)
{
// TODO: Pattern matching doesn't work in AE for non-string types
// without an explicit cast
foreach ($dataTypes as $type) {
if (!dataTypeIsStringMax($type)) {
continue;
}
foreach ($patterns[$type] as $pattern) {
$patternarray = array($pattern,
$pattern."%",
"%".$pattern,
"%".$pattern."%",
);
foreach ($patternarray as $spattern) {
// Unicode operations with AE require the PHPTYPE to be specified as
// UTF-8 and the Latin1_General_BIN2 collation. If the COLLATE
// clause is left out, we get different results between the
// encrypted and non-encrypted columns (probably because the
// collation was only changed in the encryption query).
// We must pass the length of the pattern matching string
// to the SQLTYPE instead of the field size, as we usually would,
// because otherwise we would get an empty result set.
// We need iconv_strlen to return the number of characters
// for unicode strings, since strlen returns the number of bytes.
$unicode = dataTypeIsUnicode($type);
$slength = $unicode ? iconv_strlen($spattern) : strlen($spattern);
$collate = $unicode ? " COLLATE Latin1_General_BIN2" : "";
$phptype = $unicode ? SQLSRV_PHPTYPE_STRING('UTF-8') : null;
$sqltype = $unicode ? SQLSRV_SQLTYPE_NCHAR($slength) : SQLSRV_SQLTYPE_CHAR($slength);
$param = array(array($spattern, SQLSRV_PARAM_IN, $phptype, $sqltype));
$AEQuery = "SELECT ".$colNames[$type]."_AE FROM $tableName WHERE ".$colNames[$type]."_AE LIKE ?".$collate;
$nonAEQuery = "SELECT ".$colNames[$type]." FROM $tableName WHERE ".$colNames[$type]." LIKE ?".$collate;
$AEstmt = sqlsrv_prepare($conn, $AEQuery, $param);
if (!$AEstmt) {
print_r(sqlsrv_errors());
die("Preparing AE statement for comparison failed! Comparison $comparison, type $type\n");
}
$nonAEstmt = sqlsrv_prepare($conn, $nonAEQuery, $param);
if (!$nonAEstmt) {
print_r(sqlsrv_errors());
die("Preparing non-AE statement for comparison failed! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $pattern, $type);
}
}
}
}
// Check that the expected errors ($codes) is found in the output of sqlsrv_errors() ($errors)
function checkErrors($errors, ...$codes)
{
$codeFound = false;
foreach ($codes as $code) {
if ($code[0]==$errors[0][0] and $code[1]==$errors[0][1]) {
$codeFound = true;
break;
}
}
if ($codeFound == false) {
echo "Error: ";
print_r($errors);
echo "\nExpected: ";
print_r($codes);
echo "\n";
die("Error code not found.\n");
}
}
function isEnclaveEnabled($key)
{
return (strpos($key, '-enclave') !== false);
}
function dataTypeIsString($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar"]));
}
function dataTypeIsStringMax($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeNeedsCollate($dataType)
{
return (in_array($dataType, ["char", "nchar", "varchar", "nvarchar", "varchar(max)", "nvarchar(max)"]));
}
function dataTypeIsUnicode($dataType)
{
return (in_array($dataType, ["nchar", "nvarchar", "nvarchar(max)"]));
}
function getSQLType($type, $length)
{
switch($type)
{
case "bigint":
return SQLSRV_SQLTYPE_BIGINT;
case "integer":
return SQLSRV_SQLTYPE_INT;
case "smallint":
return SQLSRV_SQLTYPE_SMALLINT;
case "tinyint":
return SQLSRV_SQLTYPE_TINYINT;
case "bit":
return SQLSRV_SQLTYPE_BIT;
case "real":
return SQLSRV_SQLTYPE_REAL;
case "float":
case "double":
return SQLSRV_SQLTYPE_FLOAT;
case "numeric":
return SQLSRV_SQLTYPE_NUMERIC(18,0);
case "time":
return SQLSRV_SQLTYPE_TIME;
case "date":
return SQLSRV_SQLTYPE_DATE;
case "datetime":
return SQLSRV_SQLTYPE_DATETIME;
case "datetime2":
return SQLSRV_SQLTYPE_DATETIME2;
case "datetimeoffset":
return SQLSRV_SQLTYPE_DATETIMEOFFSET;
case "smalldatetime":
return SQLSRV_SQLTYPE_SMALLDATETIME;
case "money":
return SQLSRV_SQLTYPE_MONEY;
case "smallmoney":
return SQLSRV_SQLTYPE_SMALLMONEY;
case "xml":
return SQLSRV_SQLTYPE_XML;
case "uniqueidentifier":
return SQLSRV_SQLTYPE_UNIQUEIDENTIFIER;
case "char":
return SQLSRV_SQLTYPE_CHAR($length);
case "varchar":
return SQLSRV_SQLTYPE_VARCHAR($length);
case "varchar(max)":
return SQLSRV_SQLTYPE_VARCHAR('max');
case "nchar":
return SQLSRV_SQLTYPE_NCHAR($length);
case "nvarchar":
return SQLSRV_SQLTYPE_NVARCHAR($length);
case "nvarchar(max)":
return SQLSRV_SQLTYPE_NVARCHAR('max');
case "binary":
case "varbinary":
case "varbinary(max)":
// Using a binary type here produces a 'Restricted data type attribute violation'
return SQLSRV_SQLTYPE_BIGINT;
default:
die("Case is missing for $type type in getSQLType.\n");
}
}
?>

View file

@ -15,7 +15,6 @@ function formulateSetupQuery($tableName, &$dataTypes, &$columns, &$insertQuery)
{
$columns = array();
$queryTypes = "(";
$queryTypesAE = "(";
$valuesString = "VALUES (";
$numTypes = sizeof($dataTypes);

View file

@ -0,0 +1,113 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to 'enabled', which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with ColumnEncryption set to 'enabled'.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt)
{
print_r(editErrors(sqlsrv_errors()));
die("Table creation failed!");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with ColumnEncryption set to 'enabled'
$newAttestation = 'enabled';
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'enabled');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'enabled');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
$splitDataTypes = array_chunk($dataTypes, 5);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
$encryptionFailed = true;
continue;
}
continue;
} else {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType!\n");
}
}
if ($encryptionFailed) {
continue;
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,138 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating a plaintext table
each time, then trying to encrypt it with different combinations of enclave-enabled and non-enclave keys
and encryption types. It then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different target combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create a table in plaintext with two columns for each AE-supported data type.
2. Insert some data in plaintext.
3. Encrypt one column for each data type.
4. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result
to the same query on the corresponding non-AE column for each data type.
5. Ensure the two results are the same.
6. Re-encrypt the table using new key and/or encryption type.
7. Compare computations as in 4. above.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create and populate a non-encrypted table
$createQuery = constructCreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt) {
print_r(sqlsrv_errors());
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitDataTypes = array_chunk($dataTypes, 5);
foreach ($splitDataTypes as $split)
{
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $key, $encryptionType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
$encryptionFailed = false;
if(!$stmt) {
if (!isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have!\n");
}
} else {
if (!isEnclaveEnabled($key)) {
die("Encrypting should have failed with key $key and encryption type $encryptionType\n");
}
}
}
}
if ($encryptionFailed) continue;
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Try re-encrypting the table
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have!\n");
}
} else {
if (!isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,71 @@
--TEST--
Test various settings for the ColumnEncryption keyword.
--DESCRIPTION--
For AE v2, the Column Encryption keyword must be set to [protocol]/[attestation URL].
If [protocol] is wrong, connection should fail; if the URL is wrong, connection
should succeed. This test sets ColumnEncryption to three values:
1. Random nonsense, which is interpreted as an incorrect protocol
so connection should fail.
2. Incorrect protocol with a correct attestation URL, connection should fail.
3. Correct protocol and incorrect URL, connection should succeed.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
// Test with random nonsense. Connection should fail.
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>"xyz",
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
} else {
die("Connecting with nonsense should have failed!\n");
}
// Test with incorrect protocol and good attestation URL. Connection should fail.
// Insert a rogue 'x' into the protocol part of the attestation.
$comma = strpos($attestation, ',');
$badProtocol = substr_replace($attestation, 'x', $comma, 0);
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>$badProtocol,
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
} else {
die("Connecting with a bad attestation protocol should have failed!\n");
}
// Test with good protocol and incorrect attestation URL. Connection should succeed
// because the URL is only checked when an enclave computation is attempted.
$badURL = substr_replace($attestation, 'x', $comma+1, 0);
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'ColumnEncryption'=>$badURL,
);
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
print_r(sqlsrv_errors());
die("Connecting with a bad attestation URL should have succeeded!\n");
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,110 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
2. Insert some data.
3. Perform rich computations on each AE-enabled column (comparisons and pattern matching) and compare the result
to the same query on the corresponding non-AE column for each data type.
4. Ensure the two results are the same.
5. Re-encrypt the table using new key and/or encryption type.
6. Compare computations as in 4. above.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
$conn = connect($server, $attestation);
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt) {
print_r(sqlsrv_errors());
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'correct');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
// Split the data type array, because for some reason we get an error
// if the query is too long (>2000 characters)
// TODO: This is a known issue, follow up on it.
$splitDataTypes = array_chunk($dataTypes, 5);
$encryptionFailed = false;
foreach ($splitDataTypes as $split) {
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $split, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
$encryptionFailed = true;
continue;
} else {
print_r(sqlsrv_errors());
die("Encrypting failed when it shouldn't have! key = $targetKey and type = $targetType\n");
}
continue;
} else {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
die("Encrypting should have failed with key $targetKey and encryption type $encryptionType\n");
}
}
}
if ($encryptionFailed) {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, 'correct');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, 'correct');
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,93 @@
--TEST--
Try re-encrypting a table with ColumnEncryption set to the wrong attestation URL, which should fail.
--DESCRIPTION--
This test cycles through $encryptionTypes and $keys, creating an encrypted table
each time, then cycles through $targetTypes and $targetKeys to try re-encrypting
the table with different combinations of enclave-enabled and non-enclave keys
and encryption types.
The sequence of operations is the following:
1. Connect with correct attestation information.
2. Create an encrypted table with two columns for each AE-supported data type, one encrypted and one not encrypted.
3. Insert some data.
4. Disconnect and reconnect with a faulty attestation URL.
5. Test comparison and pattern matching by comparing the results for the encrypted and non-encrypted columns.
Equality should work with deterministic encryption as in AE v1, but other computations should fail.
6. Try re-encrypting the table. This should fail.
--SKIPIF--
<?php require("skipif_not_hgs.inc"); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("AE_v2_values.inc");
require_once("sqlsrv_AE_functions.inc");
$initialAttestation = $attestation;
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($keys as $key) {
foreach ($encryptionTypes as $encryptionType) {
// $count is used to ensure we only run testCompare and
// testPatternMatch once for the initial table
$count = 0;
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($server, $initialAttestation);
// Create an encrypted table
$createQuery = constructAECreateQuery($tableName, $dataTypes, $colNames, $colNamesAE, $slength, $key, $encryptionType);
$insertQuery = constructInsertQuery($tableName, $dataTypes, $colNames, $colNamesAE);
$stmt = sqlsrv_query($conn, "DROP TABLE IF EXISTS $tableName");
$stmt = sqlsrv_query($conn, $createQuery);
if(!$stmt) {
print_r(sqlsrv_errors());
die("Creating an encrypted table failed when it shouldn't have!\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
unset($conn);
// Reconnect with a faulty attestation URL
$comma = strpos($attestation, ',');
$newAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$conn = connect($server, $newAttestation);
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, 'wrongurl');
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, 'wrongurl');
}
++$count;
if ($key == $targetKey and $encryptionType == $targetType) {
continue;
}
$alterQuery = constructAlterQuery($tableName, $colNamesAE, $dataTypes, $targetKey, $targetType, $slength);
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if (!isEnclaveEnabled($key) or !isEnclaveEnabled($targetKey)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
}
} else {
die("Encrypting should have failed with key $targetKey and encryption type $targetType\n");
}
}
}
}
}
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -1,8 +1,8 @@
--TEST--
Test the existence of Windows Always Encrypted keys generated in the database setup
--DESCRIPTION--
This test iterates through the rows of sys.column_master_keys and/or
sys.column_encryption_keys to look for the specific column master key and
This test iterates through the rows of sys.column_master_keys and/or
sys.column_encryption_keys to look for the specific column master key and
column encryption key generated in the database setup
--SKIPIF--
<?php require('skipif_unix.inc'); ?>
@ -44,8 +44,8 @@ if (AE\IsQualified($conn)) {
sqlsrv_free_stmt($stmt);
}
echo "Test Successfully done.\n";
echo "Test successfully done.\n";
sqlsrv_close($conn);
?>
--EXPECT--
Test Successfully done.
Test successfully done.