Ae v2 extended tests (#1077)

* Added extended AE v2 tests

* Added binary types to error check

* Updated test descriptions

* Added the test matrix

* Refactored tests

* Added else check for keystore

* Debugging connection failures

* Debugging connection failures

* Debugging connection failures

* Addressed review comments

* Fixed parse error

* Fixed parse error

* Fixed parse error

* Addressed review comments
This commit is contained in:
David Puglielli 2020-02-03 14:39:28 -08:00 committed by GitHub
parent e7b5a88364
commit 71b9d40711
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 2541 additions and 0 deletions

View file

@ -0,0 +1,220 @@
<?php
include("MsSetup.inc");
$tableName = "aev2test";
// Array of possible ColumnEncryption values. $ceValues is for the initial
// connection, $targetCeValues is for reconnection. The array keys are to be
// passed to testCompare and testPatternMatch so we don't have to pass the
// actual attestation info, which resides in MsSetup.inc.
// For incorrect protocol and attestation URL, insert a rogue 'x' on either side
// of the comma.
$comma = strpos($attestation, ',');
$wrongProtocol = substr_replace($attestation, 'x', $comma, 0);
$wrongAttestation = substr_replace($attestation, 'x', $comma+1, 0);
$ceValues = array('correct' =>$attestation,
'enabled' =>'enabled',
'disabled'=>'disabled',
'invalid' =>$wrongProtocol,
'wrongurl'=>$wrongAttestation,
);
$targetCeValues = array('correct' =>$attestation,
'enabled' =>'enabled',
'disabled'=>'disabled',
'invalid' =>$wrongProtocol,
'wrongurl'=>$wrongAttestation,
);
// 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-noenclave",
"CEK-win-enclave2",
"CEK-win-noenclave2"
);
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave",
"CEK-akv-noenclave"
);
$targetKeys = array("CEK-akv-enclave",
"CEK-akv-noenclave",
"CEK-akv-enclave2",
"CEK-akv-noenclave2"
);
} else {
die("No keystore specified! Aborting...\n");
}
// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Deterministic",
"Randomized",
);
$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, split into two arrays because if we try one array,
// at some point we get a CE405 error for no clear reason (might be a memory issue?).
// TODO: Follow up and see if we can use a single type array.
$dataTypes1 = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
'smalldatetime',
);
$dataTypes2 = array('char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
'binary',
'varbinary',
'varbinary(max)',
);
// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames1 = array();
$colNamesAE1 = array();
$colNames2 = array();
$colNamesAE2 = array();
foreach ($dataTypes1 as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames1[$type] = "c_".$column;
$colNamesAE1[$type] = "c_".$column."_AE";
}
foreach ($dataTypes2 as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames2[$type] = "c_".$column;
$colNamesAE2[$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.0001,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:00','1985-03-31 12:40:00','2025-07-23 05:00:00','1999-12-31 00:00:00',
'1956-02-27 23:59:00','2018-09-01 14:35:00','2079-06-06 23:59:00','1931-10-04 19:52:00');
// 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('3AD2BBC2', '7201', 'EED3A109F8F7745C', 'D6C3E0E11A25F3','4EACCEF38788F9','FFFFFFFFFFFFFFFF', '6230974598','44EE4A');
$testValues['varbinary'] = array('583412','F3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC8889','E43004FF', '000005ED309D3A45','06CADEF379','8041','00000000000000','D7209FFE44');
$testValues['varbinary(max)'] = array('EF409CB33408', 'D3EA762C78FC', '00','FFFFFFFFFFFFFFFFFFFFFFFFFF',
'582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1FE',
'92300943891097','DDD4D88C4B80089D2E4A','88F8A8');
// 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' => '44EE4A',
'varbinary' => 'E43004FF',
'varbinary(max)' => 'D3EA762C78FC',
'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:00',
);
// String patterns to test with LIKE
// For AE, LIKE only works with string types for now. Additional types
// are listed here because eventually the type conversions required for
// pattern matching non-string types should be supported.
$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('44EE4A'),
'varbinary' => array('E43004FF'),
'varbinary(max)' => array('D3EA762C78FC'),
'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'),
);
?>

58
test/extended/MsSetup.inc Normal file
View file

@ -0,0 +1,58 @@
<?php
/*
Microsoft SQL Server Driver for PHP - Unit Test Framework
Copyright (c) Microsoft Corporation. All rights reserved.
Description:
Global variables defining the execution context
*/
$PhpDriver = "Microsoft SQL Server Driver for PHP";
$server = 'TARGET_SERVER';
$database = 'TARGET_DATABASE';
$userName = 'TARGET_USERNAME';
$userPassword = 'TARGET_PASSWORD';
$tableName = "php_test_table";
$tableIndex = "php_test_table_index";
$procName = "php_test_proc";
$fileName = "php_test_file.dat";
$driver = "ODBC Driver 17 for SQL Server";
$connectionOptions = array("Database" => $database, "UID" => $userName, "PWD" => $userPassword, "TraceOn" => false, "Driver" => $driver);
$daasMode = false;
$marsMode = true;
$traceEnabled = false;
$adServer = 'TARGET_AD_SERVER';
$adDatabase = 'TARGET_AD_DATABASE';
$adUser = 'TARGET_AD_USERNAME';
$adPassword = 'TARGET_AD_PASSWORD';
if (isset($_ENV['MSSQL_SERVER']) || isset($_ENV['MSSQL_USER']) || isset($_ENV['MSSQL_PASSWORD'])) {
$server = $_ENV['MSSQL_SERVER'];
$uid = $_ENV['MSSQL_USER'];
$pwd = $_ENV['MSSQL_PASSWORD'];
$databaseName = $_ENV['MSSQL_DATABASE_NAME'];
} else {
$uid = $userName;
$pwd = $userPassword;
$databaseName = $database;
}
// column encryption variables
$keystore = "none"; // key store provider, acceptable values are none, win, ksp, akv
$dataEncrypted = false; // whether data is to be encrypted
// for Azure Key Vault
$AKVKeyStoreAuthentication = 'TARGET_AKV_AUTH'; // can be KeyVaultPassword or KeyVaultClientSecret
$AKVPrincipalName = 'TARGET_AKV_PRINCIPAL_NAME'; // for use with KeyVaultPassword
$AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPassword
$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,911 @@
<?php
// runPlaintextTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place plaintext encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
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 ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// 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 a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
// Encrypt the table
// 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);
$isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType);
}
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
// 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, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// runEncryptedTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place re-encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
// Cannot create a table with encrypted data if CE is disabled
// TODO: Since we can create an empty encrypted table with
// CE disabled, account for the case where CE is disabled.
if ($ceValue == 'disabled') continue;
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 ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
$conn->query("DBCC FREEPROCCACHE");
// Create and populate 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 at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
$ceDisabled = ($attestationType == 'disabled') ? true : false;
insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled);
$isEncrypted = true;
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
$initiallyEnclaveEncryption = isEnclaveEnabled($key);
// 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, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// Connect and clear the procedure cache
function connect($attestationInfo)
{
require("MsSetup.inc");
$options = "sqlsrv:Server=$server;Database=$databaseName;ColumnEncryption=$attestationInfo";
if ($keystore == 'akv') {
$securityInfo = '';
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$securityInfo .= ";KeyStorePrincipalId=$AKVPrincipalName";
$securityInfo .= ";KeyStoreSecret=$AKVPassword";
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$securityInfo .= ";KeyStoreAuthentication=$AKVKeyStoreAuthentication";
$securityInfo .= ";KeyStorePrincipalId=$AKVClientID";
$securityInfo .= ";KeyStoreSecret=$AKVSecret";
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options .= $securityInfo;
}
try {
$conn = new PDO($options, $uid, $pwd);
} catch (PDOException $error) {
$e = $error->errorInfo;
checkErrors($e, array('CE400', '0'));
return false;
}
$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);
if (!$stmt) {
print_r($conn->errorInfo());
die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n");
} else {
$info = $stmt->fetch();
if (empty($info) or ($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");
unset($stmt);
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, $ceDisabled=false)
{
if (empty($testValues)) {
die("$testValues is empty or non-existent. Please check the required values file.\n");
}
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// Insert the data using PDO::prepare()
try {
$stmt = $conn->prepare($insertQuery);
$i=1;
foreach ($dataTypes as $type) {
$PDOType = getPDOType($type);
if (!dataTypeIsBinary($type)) {
$stmt->bindParam($i, $testValues[$type][$v], $PDOType);
$stmt->bindParam($i+1, $testValues[$type][$v], $PDOType);
} else {
// unset() is necessary because otherwise the same data may be
// inserted into multiple binary columns.
unset($val);
$val=pack('H*', $testValues[$type][$v]);
$stmt->bindParam($i, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
$stmt->bindParam($i+1, $val, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
}
$i+=2;
}
$stmt->execute();
} catch (PDOException $error) {
if (!$ceDisabled) {
print_r($error);
die("Inserting values in encrypted table failed\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('22018', '206'));
}
}
}
unset($stmt);
}
// encryptTable attempts to encrypt the table in place and verifies
// if it works given the attestation info and key type.
// Arguments:
// resource $conn: The connection
// string $alterQuery: The query to encrypt the table
// 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', 'disabled', or 'wrongurl'
// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting
// as for initial encryption.
// bool $initialEncryption: Whether we are testing with table initially encrypted, instead
// of plaintext being encrypted after creation
// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an
// enclave-enabled key
function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false)
{
try {
$stmt = $conn->query($alterQuery);
if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) {
die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n");
}
} catch (PDOException $error) {
if ($sameKeyAndType) {
print_r($error);
die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n");
} elseif ($initialEncryption and !$initallyEnclaveEncrypted) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
} elseif ($attestation == 'correct') {
if (isEnclaveEnabled($key)) {
print_r($error);
die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'enabled' or $attestation == 'disabled') {
if (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33546'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'wrongurl') {
if (isEnclaveEnabled($key)) {
$e = $error->errorInfo;
checkErrors($e, array('CE405', '0'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'invalid') {
die("Encrypting table with invalid protocol! Should not get here!\n");
} else {
die("Error! This is no-man's-land\n");
}
return false;
}
unset($stmt);
return true;
}
// 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, $isEncrypted, $comparison='', $type='')
{
try {
$nonAEstmt->execute();
} catch(Exception $error) {
print_r($error);
die("Executing non-AE computation statement failed!\n");
}
try {
$AEstmt->execute();
} catch(Exception $error) {
if (!$isEncrypted) {
die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n");
}
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r($error);
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\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: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
$e = $error->errorInfo;
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\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: attestation $attestation, key $key and encryption type $encryptionType");
}
} 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: attestation $attestation, key $key and encryption type $encryptionType\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: attestation $attestation, key $key and encryption type $encryptionType\n");
}
} elseif ($attestation == 'disabled') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
} elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') {
$e = $error->errorInfo;
checkErrors($e, array('22018', '206'));
} else {
$e = $error->errorInfo;
checkErrors($e, array('42000', '33277'));
}
} else {
print_r($error);
die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\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' or $type == 'binary') {
// 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
// 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'
// bool $isEncrypted: Whether the table is encrypted
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted)
{
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);
unset($threshold);
$threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$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);
$nonAEstmt = $conn->prepare($nonAEQuery);
if (!dataTypeIsBinary($type)) {
$AEstmt->bindParam(1, $threshold, $PDOType);
$nonAEstmt->bindParam(1, $threshold, $PDOType);
} else {
$AEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
$nonAEstmt->bindParam(1, $threshold, $PDOType, 0, PDO::SQLSRV_ENCODING_BINARY);
}
} catch (PDOException $error) {
print_r($error);
die("Preparing/binding statements for comparison failed! Comparison $comparison, type $type");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $comparison, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
// 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: Strings to pattern match, 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', 'disabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types.
// This is for security reasons, follow up on it.
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);
$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;
// TODO: Add binary type support below. May need to use unset()
// as in insertValues().
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! Comparison $comparison, type $type\n");
}
compareResults($AEstmt, $nonAEstmt, $key, $encryptionType, $attestation, $isEncrypted, $pattern, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
}
// Check that the expected errors ($codes) is found in the PDOException ($errors)
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, ["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 dataTypeIsBinary($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "varbinary(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,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $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 reconnects
and cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $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 reconnects
and cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
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");
}
?>

View file

@ -0,0 +1,991 @@
<?php
// runPlaintextTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place plaintext encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
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 ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// 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 a plaintext table failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
insertValues($conn, $insertQuery, $dataTypes, $testValues);
// Encrypt the table
// 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);
$isEncrypted = encryptTable($conn, $alterQuery, $key, $encryptionType, $attestationType);
}
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if ($key == $targetKey and $encryptionType == $targetType and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
// 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, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// runEncryptedTest is the main function that cycles through the
// ColumnEncryption keywords, keys, and encryption types, testing
// in-place re-encryption and rich computations. The arguments
// all come from AE_v2_values.inc.
// Arguments:
// array $ceValues: ColumnEncryption keywords/attestation URLs
// array $keys: Encryption keys
// array $encryptionTypes: Encryption types (Deterministic, Randomized)
// array $targetCeValues: ColumnEncryption keywords/attestation URLs on reconnection
// array $targetKeys: Encryption keys on reconnection
// array $targetTypes: Encryption types on reconnection
// string $tableName: Name of table used for testing
// array $dataTypes: Data types going into the table
// array $colNames: Plaintext column names
// array $colNamesAE: Encrypted column names
// integer $length: Size of string columns
// string $slength: $length as a string
// array $testValues: Data to be inserted into the table
// array $comparisons: The comparison operators
// array $patterns: Values to pattern match against
// array $thresholds: Values to use comparison operators against
function runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes, $colNames, $colNamesAE,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds)
{
// Create a table for each key and encryption type, re-encrypt using each
// combination of target key and target encryption
foreach ($ceValues as $attestationType=>$ceValue) {
// Cannot create a table with encrypted data if CE is disabled
// TODO: Since we can create an empty encrypted table with
// CE disabled, account for the case where CE is disabled.
if ($ceValue == 'disabled') continue;
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 ($targetCeValues as $targetAttestationType=>$targetCeValue) {
foreach ($targetKeys as $targetKey) {
foreach ($targetTypes as $targetType) {
$conn = connect($ceValue);
if (!$conn) {
if ($attestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($attestationType == 'invalid') {
die("Connection should have failed for invalid protocol at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
// Free the encryption cache to avoid spurious 'operand type clash' errors
sqlsrv_query($conn, "DBCC FREEPROCCACHE");
// Create and populate 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 at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
$ceDisabled = $attestationType == 'disabled' ? true : false;
insertValues($conn, $insertQuery, $dataTypes, $testValues, $ceDisabled);
$isEncrypted = true;
// Test rich computations
if ($count == 0) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestationType, $isEncrypted);
}
++$count;
// $sameKeyAndType is used when checking re-encryption, because no error is returned
$sameKeyAndType = false;
if (($key == $targetKey) and ($encryptionType == $targetType) and $isEncrypted) {
$sameKeyAndType = true;
}
// Disconnect and reconnect with the target ColumnEncryption keyword value
unset($conn);
$conn = connect($targetCeValue);
if (!$conn) {
if ($targetAttestationType == 'invalid') {
continue;
} else {
print_r(sqlsrv_errors());
die("Connection failed when it shouldn't have at ColumnEncryption = $ceValue, key = $key, type = $encryptionType, targets $targetCeValue, $targetKey, $targetType\n");
}
} elseif ($targetAttestationType == 'invalid') {
continue;
}
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
// Re-encrypt the table
$initiallyEnclaveEncryption = isEnclaveEnabled($key);
// 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, $targetKey, $targetType, $slength);
$encryptionSucceeded = encryptTable($conn, $alterQuery, $targetKey, $targetType, $targetAttestationType, $sameKeyAndType, true, $initiallyEnclaveEncryption);
}
// Test rich computations
if ($encryptionSucceeded) {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $targetKey, $targetType, $targetAttestationType,true);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $targetKey, $targetType, $targetAttestationType, true);
} else {
testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $targetAttestationType, $isEncrypted);
testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $targetAttestationType, $isEncrypted);
}
unset($conn);
}
}
}
}
}
}
}
// Connect and clear the procedure cache
function connect($attestationInfo)
{
require("MsSetup.inc");
$options = array('database'=>$database,
'uid'=>$userName,
'pwd'=>$userPassword,
'CharacterSet'=>'UTF-8',
'ColumnEncryption'=>$attestationInfo,
'TraceOn'=>true,
'TraceOn'=>'c:\Users\davidp\Documents\SQL.LOG',
);
if ($keystore == 'akv') {
if ($AKVKeyStoreAuthentication == 'KeyVaultPassword') {
$securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVPrincipalName,
'KeyStoreSecret'=>$AKVPassword,
);
} elseif ($AKVKeyStoreAuthentication == 'KeyVaultClientSecret') {
$securityInfo = array('KeyStoreAuthentication'=>$AKVKeyStoreAuthentication,
'KeyStorePrincipalId'=>$AKVClientID,
'KeyStoreSecret'=>$AKVSecret,
);
} else {
die("Incorrect value for KeyStoreAuthentication keyword!\n");
}
$options = array_merge($options, $securityInfo);
}
$conn = sqlsrv_connect($server, $options);
if (!$conn) {
$e = sqlsrv_errors();
checkErrors($e, array('CE400', '0'));
return false;
}
else
{
// 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);
if (!$stmt) {
print_r(sqlsrv_errors());
die("Error when checking if enclave computations are enabled. This should never happen! Non-HGS servers should have been skipped.\n");
} else {
$info = sqlsrv_fetch_array($stmt);
if (empty($info) or ($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, $ceDisabled=false)
{
global $length;
if (empty($testValues)) {
die("$testValues is empty or non-existent. Please check the required values file.\n");
}
for ($v = 0; $v < sizeof($testValues['bigint']); ++$v) {
$insertValues = array();
// Use pack() on binary data
$params = array();
foreach ($dataTypes as $type) {
$SQLType = getSQLType($type, $length);
$PHPType = getPHPType($type);
$val = dataTypeIsBinary($type) ? pack('H*', $testValues[$type][$v]) : $testValues[$type][$v];
$params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType);
$params[] = array($val, SQLSRV_PARAM_IN, $PHPType, $SQLType);
}
// Insert the data using sqlsrv_prepare()
$stmt = sqlsrv_prepare($conn, $insertQuery, $params);
if ($stmt == false) {
if (!$ceDisabled) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at prepare\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
}
}
if (sqlsrv_execute($stmt) == false) {
if (!$ceDisabled) {
print_r(sqlsrv_errors());
die("Inserting values in encrypted table failed at execute\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
}
}
sqlsrv_free_stmt($stmt);
}
}
// encryptTable attempts to encrypt the table in place and verifies
// if it works given the attestation info and key type.
// Arguments:
// resource $conn: The connection
// string $alterQuery: The query to encrypt the table
// 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', 'disabled', or 'wrongurl'
// bool $sameKeyAndType: Whether the key and encryption type are same for re-encrypting
// as for initial encryption.
// bool $initialEncryption: Whether we are testing with table initially encrypted, instead
// of plaintext being encrypted after creation
// bool $initiallyEnclaveEncrypted: Whether the table was initally encrypted with an
// enclave-enabled key
function encryptTable($conn, $alterQuery, $key, $encryptionType, $attestation, $sameKeyAndType=false, $initialEncryption=false, $initallyEnclaveEncrypted=false)
{
$stmt = sqlsrv_query($conn, $alterQuery);
if(!$stmt) {
if ($sameKeyAndType) {
print_r(sqlsrv_errors());
die("Encrypting table should not fail when target encryption key and type are the same as source: attestation $attestation, key $key and encryption type $encryptionType\n");
} elseif ($initialEncryption and !$initallyEnclaveEncrypted) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
} elseif ($attestation == 'correct') {
if (isEnclaveEnabled($key)) {
print_r(sqlsrv_errors());
die("Encrypting with correct attestation failed when it shouldn't have: attestation $attestation, key $key and encryption type $encryptionType\n");
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'enabled' or $attestation == 'disabled') {
if (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33546'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'wrongurl') {
if (isEnclaveEnabled($key)) {
$e = sqlsrv_errors();
checkErrors($e, array('CE405', '0'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33543'));
}
} elseif ($attestation == 'invalid') {
die("Encrypting table with invalid protocol! Should not get here!\n");
} else {
die("Error! This is no-man's-land\n");
}
return false;
} else {
if ((!isEnclaveEnabled($key) or $attestation != 'correct') and !$sameKeyAndType) {
die("Encrypting should have failed with attestation $attestation, key $key and encryption type $encryptionType\n");
}
unset($stmt);
return true;
}
}
// 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, $isEncrypted, $comparison='', $type='')
{
if (!sqlsrv_execute($nonAEstmt)) {
print_r(sqlsrv_errors());
die("Executing non-AE computation statement failed!\n");
}
if(!sqlsrv_execute($AEstmt)) {
if (!$isEncrypted) {
die("Computation statement execution should not have failed for an unencrypted table: attestation $attestation, key $key and encryption type $encryptionType\n");
}
if ($attestation == 'enabled') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\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'));
} else {
print_r(sqlsrv_errors());
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} elseif ($attestation == 'wrongurl') {
if ($encryptionType == 'Deterministic') {
if ($comparison == '=') {
print_r(sqlsrv_errors());
die("Equality comparison failed for deterministic encryption: attestation $attestation, key $key and encryption type $encryptionType\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'));
} else {
print_r(sqlsrv_errors());
die("AE statement execution failed when it shouldn't: attestation $attestation, key $key and encryption type $encryptionType");
}
} 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: attestation $attestation, key $key and encryption type $encryptionType\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: attestation $attestation, key $key and encryption type $encryptionType\n");
}
} elseif ($attestation == 'disabled') {
if (!isEnclaveEnabled($key) and $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
} elseif ($comparison == '=' or $comparison == '<>' or $encryptionType == 'Randomized') {
$e = sqlsrv_errors();
checkErrors($e, array('22018', '206'));
} else {
$e = sqlsrv_errors();
checkErrors($e, array('42000', '33277'));
}
} else {
print_r(sqlsrv_errors());
die("Unexpected error occurred in compareResults: attestation $attestation, key $key and encryption type $encryptionType\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' or $type == 'binary') {
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: 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
// 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'
// bool $isEncrypted: Whether the table is encrypted
function testCompare($conn, $tableName, $comparisons, $dataTypes, $colNames, $thresholds, $length, $key, $encryptionType, $attestation, $isEncrypted)
{
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 = getPHPType($type);
$threshold = dataTypeIsBinary($type) ? pack('H*', $thresholds[$type]) : $thresholds[$type];
$param = array(array($threshold, 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, $isEncrypted, $comparison, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
// 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: Strings to pattern match, 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', 'disabled', or 'wrongurl'
// bool $isEncrypted: Whether the table is encrypted
function testPatternMatch($conn, $tableName, $patterns, $dataTypes, $colNames, $key, $encryptionType, $attestation, $isEncrypted)
{
foreach ($dataTypes as $type) {
// TODO: Pattern matching doesn't work in AE for non-string types.
// This is for security reasons, follow up on it.
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;
// TODO: Add binary type support below. May need to use unset()
// as in insertValues().
$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, $isEncrypted, $pattern, $type);
unset($AEstmt);
unset($nonAEstmt);
}
}
}
}
// 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, ["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 dataTypeIsBinary($dataType)
{
return (in_array($dataType, ["binary", "varbinary", "varbinary(max)"]));
}
function getPHPType($type)
{
switch($type) {
case "bigint":
case "integer":
case "smallint":
case "tinyint":
case "bit":
return SQLSRV_PHPTYPE_INT;
break;
case "real":
case "float":
case "double":
return SQLSRV_PHPTYPE_FLOAT;
break;
case "numeric":
case "money":
case "smallmoney":
case "time":
case "date":
case "datetime":
case "datetime2":
case "datetimeoffset":
case "smalldatetime":
case "xml":
case "uniqueidentifier":
case "char":
case "varchar":
case "varchar(max)":
return SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR);
break;
case "nchar":
case "nvarchar":
case "nvarchar(max)":
return SQLSRV_PHPTYPE_STRING('UTF-8');
break;
case "binary":
case "varbinary":
case "varbinary(max)":
return SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY);
break;
default:
die("Case is missing for $type type in GetPHPType.\n");
}
}
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":
return SQLSRV_SQLTYPE_BINARY($length);
break;
case "varbinary":
return SQLSRV_SQLTYPE_VARBINARY($length);
break;
case "varbinary(max)":
return SQLSRV_SQLTYPE_VARBINARY('max');
break;
default:
die("Case is missing for $type type in getSQLType.\n");
}
}
?>

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $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 reconnects
and cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,41 @@
--TEST--
Test rich computations and in-place encryption of plaintext with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $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 reconnects
and cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
7. Compare computations as in 4. above.
8. Re-encrypt the table using a new key and/or encryption type.
9. Compare computations as in 4. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runPlaintextTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests nonstring types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes1, $colNames1, $colNamesAE1,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -0,0 +1,39 @@
--TEST--
Test rich computations and in place re-encryption with AE v2.
--DESCRIPTION--
This test cycles through $ceValues, $encryptionTypes, and $keys, creating
an encrypted table each time, then cycles through $targetCeValues, $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. Disconnect and reconnect with a new value for ColumnEncryption.
6. Compare computations as in 3. above.
7. Re-encrypt the table using a new key and/or encryption type.
8. Compare computations as in 3. above.
This test only tests string types, because if we try to tests all types at
once, eventually a CE405 error is returned.
--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");
runEncryptedTest($ceValues, $keys, $encryptionTypes,
$targetCeValues, $targetKeys, $targetTypes,
$tableName, $dataTypes2, $colNames2, $colNamesAE2,
$length, $slength, $testValues,
$comparisons, $patterns, $thresholds);
echo "Done.\n";
?>
--EXPECT--
Done.

View file

@ -42,6 +42,8 @@ if __name__ == '__main__':
parser.add_argument('-dbname', '--DBNAME', required=True)
parser.add_argument('-azure', '--AZURE', required=False, default='no')
args = parser.parse_args()
print("Start\n")
try:
server = os.environ['TEST_PHP_SQL_SERVER']
@ -61,10 +63,13 @@ if __name__ == '__main__':
if (args.AZURE.lower() == 'no'):
manageTestDB('create_db.sql', conn_options, args.DBNAME)
print("About to set up databases...\n")
# create tables in the new database
setupTestDatabase(conn_options, args.DBNAME, args.AZURE)
print("About to populate tables...\n")
# populate these tables
populateTables(conn_options, args.DBNAME)
print("About to set up encryption...\n")
# setup AE (certificate, column master key and column encryption key)
setupAE(conn_options, args.DBNAME)