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:
parent
e7b5a88364
commit
71b9d40711
220
test/extended/AE_v2_values.inc
Normal file
220
test/extended/AE_v2_values.inc
Normal 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
58
test/extended/MsSetup.inc
Normal 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';
|
||||||
|
?>
|
911
test/extended/pdo_AE_functions.inc
Normal file
911
test/extended/pdo_AE_functions.inc
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
41
test/extended/pdo_aev2_plaintext_nonstring.phpt
Normal file
41
test/extended/pdo_aev2_plaintext_nonstring.phpt
Normal 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.
|
41
test/extended/pdo_aev2_plaintext_string.phpt
Normal file
41
test/extended/pdo_aev2_plaintext_string.phpt
Normal 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.
|
39
test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt
Normal file
39
test/extended/pdo_aev2_reencrypt_encrypted_nonstring.phpt
Normal 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.
|
39
test/extended/pdo_aev2_reencrypt_encrypted_string.phpt
Normal file
39
test/extended/pdo_aev2_reencrypt_encrypted_string.phpt
Normal 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.
|
36
test/extended/skipif_not_hgs.inc
Normal file
36
test/extended/skipif_not_hgs.inc
Normal 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");
|
||||||
|
}
|
||||||
|
?>
|
991
test/extended/sqlsrv_AE_functions.inc
Normal file
991
test/extended/sqlsrv_AE_functions.inc
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
41
test/extended/sqlsrv_aev2_plaintext_nonstring.phpt
Normal file
41
test/extended/sqlsrv_aev2_plaintext_nonstring.phpt
Normal 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.
|
41
test/extended/sqlsrv_aev2_plaintext_string.phpt
Normal file
41
test/extended/sqlsrv_aev2_plaintext_string.phpt
Normal 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.
|
39
test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt
Normal file
39
test/extended/sqlsrv_aev2_reencrypt_encrypted_nonstring.phpt
Normal 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.
|
39
test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt
Normal file
39
test/extended/sqlsrv_aev2_reencrypt_encrypted_string.phpt
Normal 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.
|
|
@ -43,6 +43,8 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('-azure', '--AZURE', required=False, default='no')
|
parser.add_argument('-azure', '--AZURE', required=False, default='no')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("Start\n")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server = os.environ['TEST_PHP_SQL_SERVER']
|
server = os.environ['TEST_PHP_SQL_SERVER']
|
||||||
uid = os.environ['TEST_PHP_SQL_UID']
|
uid = os.environ['TEST_PHP_SQL_UID']
|
||||||
|
@ -61,10 +63,13 @@ if __name__ == '__main__':
|
||||||
if (args.AZURE.lower() == 'no'):
|
if (args.AZURE.lower() == 'no'):
|
||||||
manageTestDB('create_db.sql', conn_options, args.DBNAME)
|
manageTestDB('create_db.sql', conn_options, args.DBNAME)
|
||||||
|
|
||||||
|
print("About to set up databases...\n")
|
||||||
# create tables in the new database
|
# create tables in the new database
|
||||||
setupTestDatabase(conn_options, args.DBNAME, args.AZURE)
|
setupTestDatabase(conn_options, args.DBNAME, args.AZURE)
|
||||||
|
print("About to populate tables...\n")
|
||||||
# populate these tables
|
# populate these tables
|
||||||
populateTables(conn_options, args.DBNAME)
|
populateTables(conn_options, args.DBNAME)
|
||||||
|
print("About to set up encryption...\n")
|
||||||
# setup AE (certificate, column master key and column encryption key)
|
# setup AE (certificate, column master key and column encryption key)
|
||||||
setupAE(conn_options, args.DBNAME)
|
setupAE(conn_options, args.DBNAME)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue