Skip to content

Commit

Permalink
Always Encrypted v2 support (#1045)
Browse files Browse the repository at this point in the history
* Change to support ae-v2

* Add support for AE V2

* Added some descriptions and comments

* Fixed PDO pattern matching

* Updated key generation scripts

* Fixed key script

* Fixed char/nchar results, fixed formatting issues

* Addressed review comments

* Updated key scripts

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Debugging aev2 keyword failure

* Added skipif to ae v2 keyword test

* Addressed review comments

* Fixed braces and camel caps

* Updated test descriptions

* Added detail to test descriptions

* Tiny change
  • Loading branch information
david-puglielli authored Oct 31, 2019
1 parent aec733b commit 0513287
Show file tree
Hide file tree
Showing 24 changed files with 2,534 additions and 41 deletions.
47 changes: 25 additions & 22 deletions source/shared/core_conn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,14 +718,14 @@ bool core_is_conn_opt_value_escaped( _Inout_ const char* value, _Inout_ size_t v

const char *pch = strchr(pstr, '}');
size_t i = 0;

while (pch != NULL && i < value_len) {
i = pch - pstr + 1;

if (i == value_len || (i < value_len && pstr[i] != '}')) {
return false;
}

i++; // skip the brace
pch = strchr(pch + 2, '}'); // continue searching
}
Expand Down Expand Up @@ -783,7 +783,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou

try {
// Since connection options access token and authentication cannot coexist, check if both of them are used.
// If access token is specified, check UID and PWD as well.
// If access token is specified, check UID and PWD as well.
// No need to check the keyword Trusted_Connection because it is not among the acceptable options for SQLSRV drivers
if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) {
bool invalidOptions = false;
Expand All @@ -801,7 +801,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
access_token_used = true;
}

// Check if Authentication is ActiveDirectoryMSI
// Check if Authentication is ActiveDirectoryMSI
// https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
bool activeDirectoryMSI = false;
if (authentication_option_used) {
Expand All @@ -813,7 +813,7 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
if (!stricmp(option, AzureADOptions::AZURE_AUTH_AD_MSI)) {
activeDirectoryMSI = true;

// There are two types of managed identities:
// There are two types of managed identities:
// (1) A system-assigned managed identity: UID must be NULL
// (2) A user-assigned managed identity: UID defined but must not be an empty string
// In both cases, PWD must be NULL
Expand All @@ -832,11 +832,11 @@ void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inou
}
}
}

// Add the server name
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string TSRMLS_CC );

// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// If uid is not present then we use trusted connection -- but not when access token or ActiveDirectoryMSI is used,
// because they are incompatible
if (!access_token_used && !activeDirectoryMSI) {
if (uid == NULL || strnlen_s(uid) == 0) {
Expand Down Expand Up @@ -1153,9 +1153,12 @@ void column_encryption_set_func::func( _In_ connection_option const* option, _In
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );

// Column Encryption is disabled by default unless it is explicitly 'Enabled'
// Column Encryption is disabled by default, but if it is present and not
// explicitly set to disabled or enabled, the ODBC driver will assume the
// user is providing an attestation protocol and URL for enclave support.
// For our purposes we need only set ce_option.enabled to true if not disabled.
conn->ce_option.enabled = false;
if ( !stricmp(value_str, "enabled" )) {
if ( stricmp(value_str, "disabled" )) {
conn->ce_option.enabled = true;
}

Expand Down Expand Up @@ -1200,7 +1203,7 @@ void ce_akv_str_set_func::func(_In_ connection_option const* option, _In_ zval*
char *pValue = static_cast<char*>(sqlsrv_malloc(value_len + 1));
memcpy_s(pValue, value_len + 1, value_str, value_len);
pValue[value_len] = '\0'; // this makes sure there will be no trailing garbage

// This will free the existing memory block before assigning the new pointer -- the user might set the value(s) more than once
if (option->conn_option_key == SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID) {
conn->ce_option.akv_id = pValue;
Expand Down Expand Up @@ -1262,10 +1265,10 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
}

const char* value_str = Z_STRVAL_P( value );
// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the

// The SQL_COPT_SS_ACCESS_TOKEN pre-connection attribute allows the use of an access token (in the format extracted from
// an OAuth JSON response), obtained from Azure AD for authentication instead of username and password, and also
// bypasses the negotiation and obtaining of an access token by the driver. To use an access token, set the
// SQL_COPT_SS_ACCESS_TOKEN connection attribute to a pointer to an ACCESSTOKEN structure
//
// typedef struct AccessToken
Expand All @@ -1276,30 +1279,30 @@ void access_token_set_func::func( _In_ connection_option const* option, _In_ zva
//
// NOTE: The ODBC Driver version 13.1 only supports this authentication on Windows.
//
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// A valid access token byte string must be expanded so that each byte is followed by a 0 padding byte,
// similar to a UCS-2 string containing only ASCII characters
//
// See https://docs.microsoft.com/sql/connect/odbc/using-azure-active-directory#authenticating-with-an-access-token

size_t dataSize = 2 * value_len;
sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;

sqlsrv_malloc_auto_ptr<ACCESSTOKEN> accToken;
accToken = reinterpret_cast<ACCESSTOKEN*>(sqlsrv_malloc(sizeof(ACCESSTOKEN) + dataSize));

ACCESSTOKEN *pAccToken = accToken.get();
SQLSRV_ASSERT(pAccToken != NULL, "Something went wrong when trying to allocate memory for the access token.");

pAccToken->dataSize = dataSize;

// Expand access token with padding bytes
for (size_t i = 0, j = 0; i < dataSize; i += 2, j++) {
pAccToken->data[i] = value_str[j];
pAccToken->data[i+1] = 0;
}

core::SQLSetConnectAttr(conn, SQL_COPT_SS_ACCESS_TOKEN, reinterpret_cast<SQLPOINTER>(pAccToken), SQL_IS_POINTER);
// Save the pointer because SQLDriverConnect() will use it to make connection to the server

// Save the pointer because SQLDriverConnect() will use it to make connection to the server
conn->azure_ad_access_token = pAccToken;
accToken.transferred();
}
163 changes: 163 additions & 0 deletions test/functional/pdo_sqlsrv/AE_v2_values.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
include("MsSetup.inc");

$tableName = "aev2test";

// Names of the encryption keys, depending on whether we are using Windows
// or AKV authentication (defined in MsSetup.inc). -enclave keys are enclave
// enabled, -noenclave keys are not enclave enabled.
// $targetKeys are the keys used for re-encrypting encrypted columns
if ($keystore == 'win') {
$keys = array("CEK-win-enclave", "CEK-win-noenclave");
$targetKeys = array("CEK-win-enclave", "CEK-win-enclave2", "CEK-win-noenclave", "CEK-win-noenclave2");
} elseif ($keystore == 'akv') {
$keys = array("CEK-akv-enclave", "CEK-akv-noenclave");
$targetKeys = array("CEK-akv-enclave", "CEK-akv-enclave2", "CEK-akv-noenclave", "CEK-akv-noenclave2");
}

// $targetTypes are the encryption types used for re-encrypting encrypted columns
$encryptionTypes = array("Randomized", "Deterministic");
$targetTypes = array("Deterministic", "Randomized");

// Length of the string-type columns. $slength is length as a string instead of integer
$length = 64;
$slength = '64';

// Testing the following data types.
// TODO: Add binary string types and fix smalldatetime issues.
$dataTypes = array('integer',
'bigint',
'smallint',
'tinyint',
'bit',
'float',
'real',
'numeric',
'char',
'nchar',
'varchar',
'nvarchar',
'varchar(max)',
'nvarchar(max)',
//'binary',
//'varbinary',
//'varbinary(max)',
'date',
'time',
'datetime',
'datetime2',
'datetimeoffset',
//'smalldatetime',
);

// Construct the array of column names. Two columns for each data type,
// one encrypted (suffixed _AE) and one not encrypted.
$colNames = array();
$colNamesAE = array();

foreach ($dataTypes as $type) {
$column = str_replace(array("(", ",", ")"), array("_", "_", ""), $type);
$colNames[$type] = "c_".$column;
$colNamesAE[$type] = "c_".$column."_AE";
}

// The test data below is a mixture of random data and edge cases
$testValues = array();

// integers
$testValues['integer'] = array(0,-1,1,2147483647,-2147483648,65536,-100000000,128,9);
$testValues['bigint'] = array(9223372036854775807,-40,0,1,2147483647,-2147483648,65536,-100000000000000);
$testValues['smallint'] = array(4,-4,-32768,-99,32767,-30000,-12,-1);
$testValues['tinyint'] = array(2,0,255,254,99,101,100,32);
$testValues['bit'] = array(1,1,0,0,0,0,1,0);

// floating point
$testValues['float'] = array(3.14159,2.3e+12,-2.3e+12,2.23e-308,1,-1.79e+308,892.3098234234001,1.2);
$testValues['real'] = array(3.14159,2.3e+12,-2.3e+12,1.18e-38,1,-3.4e+38,892.3098234234001,1.2);
$testValues['numeric'] = array(-3.14159,1.003456789,45.6789,-0.000000001,987987.12345,-987987.12345,100000000000,-100000000000);

// dates and times
$testValues['date'] = array('2010-01-31','0485-03-31','7825-07-23','9999-12-31','1956-02-27','2018-09-01','5401-11-02','1031-10-04');
$testValues['time'] = array('12:40:40','08:14:54.3096','23:59:59.9999','01:00:34.0101','21:45:45.4545','00:23:45.6','17:48:00.0000','20:31:49.0001');
$testValues['datetime2'] = array('9801-01-29 11:45:23.5092856','2384-12-31 12:40:12.5434323','1984-09-25 10:40:20.0909111','9999-12-31 23:59:59.999999',
'1259-04-29 23:59:59.9999999','1748-09-21 17:48:54.723','3125-05-31 05:00:32.4','0001-01-01 00:00:00');
$testValues['datetimeoffset'] = array('9801-01-29 11:45:23.5092856-12:45','0001-01-01 00:00:00-02:30','1984-09-25 10:40:20.0909111+03:00','1748-09-21 17:48:54.723-09:21',
'4896-05-18 23:17:58.3-02:00','1657-08-04 18:14:27.4','2022-03-17 07:31:45.890342+09:30','1987-10-25 14:27:34.6320945-06:00');
$testValues['datetime'] = array('9801-01-29 11:45:23.509','2384-12-31 12:40:12.543','1984-09-25 10:40:20.090','9999-12-31 23:59:59.997',
'2753-04-29 23:59:59.997','1948-09-21 17:48:54.723','3125-05-31 05:00:32.4','2001-01-01 00:00:00');
$testValues['smalldatetime'] = array('1998-06-13 04:00:30','1985-03-31 12:40:40','2025-07-23 05:00:32','1999-12-31 00:00:00',
'1956-02-27 23:59:59','2018-09-01 14:35:30','2079-06-06 23:59:29.997','1931-10-04 19:52:21');

// strings, ascii and unicode
$testValues['char'] = array('wvyxz', 'tposw', '%c@kj>5', 'fd4$_w@q^@!coe$7', 'abcd', 'ev72#x*fv=u$', '4rfg3sw', 'voi%###i<@@');
$testValues['nchar'] = array('⽧㘎ⷅ㪋','af㋮ᶄḉㇼ៌ӗඣ','ኁ㵮ഖᅥ㪮ኸ⮊ߒᙵꇕ⯐គꉟफ़⻦ꈔꇼŞ','ꐷꬕ','㐯㩧㖃⺵㴰ڇལᧆ겴ꕕ겑וֹꔄ若㌉ᒵȅ㗉ꗅᡉ','ʭḪぅᾔᎀ㍏겶ꅫၞ㴉ᴳ㜞҂','','בּŬḛʼꃺꌖ㓵ꗛ᧽ഭწ社⾯㬄౧ຸฬ㐯ꋛ㗾');
$testValues['varchar'] = array('gop093','*#$@@)%*$@!%','cio4*3do*$','zzz$a#l',' ','v#x%n!k&r@p$f^','6$gt?je#~','0x3dK#?');
$testValues['nvarchar'] = array('ᾁẴ㔮㖖ୱܝ㐗㴴៸ழ᷂ᵄ葉អ㺓節','ӕᏵ൴ꔓὀ⾼','','璉Džꖭ갪ụ⺭','Ӿϰᇬ㭡㇑ᵈᔆ⽹hᙎ՞ꦣ㧼ለͭ','Ĕ㬚㔈♠既','ꁈ ݫ','ꍆફⷌ㏽̗ૣܯ¢⽳㌭ゴᔓᅄѓⷉꘊⶮᏏᴗஈԋ≡ㄊହꂈ꓂ꑽრꖾŞ⽉걹ꩰോఫ㒧㒾㑷藍㵀ဲ更ꧥ');
$testValues['varchar(max)'] = array('Q0H4@4E%v+ 3*Trx#>*r86-&d$VgjZ','AjEvVABur(A&Q@eG,A$3u"xAzl','z#dFd4z',
'9Dvsg9B?7oktB@|OIqy<\K^\e|*7Y&yH31E-<.hQ:)g Jl`MQV>rdOhjG;B4wQ(WR[`l(pELt0FYu._T3+8tns!}Nq<i-Nqbu@]1<K{PS[SHSF(]G[G XPLlAUezBm^&qn^mK(&]ss6&yVxW_N_J5V*iKcgXyb+Hz:HS<9>rc1%n@|N|ik C@ 03a/ +H9mBq','SSs$Ie*{:D4;S]',' ','<\K^\e|*7Y&yH31E-<.hQ:','@Kg1Z6XTOgbt?CEJ|M^rkR_L4{1?l<e`N@');
$testValues['nvarchar(max)'] = array('xᐕᛙᘡ','ퟅ㚶Ἢœäᑐï','ꐾɔᡧ㝚ஒŪᚔᘘښ곅սꕟքꀉᎠኇ','t9p4r5',
'﹨ퟱꈽᕧු꧍ۡᢙⴖ㒘ᆾ겇ᅞ〱㝸㛾㕥গଜ㳸ꍍ匿ཋ㵔ﬠᄩ᧙ꖍᕿ㩴ఽᙿ','ⴠ⿃ᶺ͚ᎉ઄㵨጑㛢㌋㙤ᙘّᘷ㬡',
'ᵄご︵ࣲꌤꏵퟰꖛᏠƢᵙꌵ㙈㜂琢㎯㪏㐵꒚㧶ᐁቴƯɋü㶌領㻡㉉걂ꈊㇷѼμꅲڧᶀƸڍ⭩㉩㛜ꆶ㕸ꁺꖁ㓫ޘ갧ᛄ㶋㘚ᐋꗡͭచ㖔፟ꐸ㱯ⵜᥰꃷᇂὥ㗍㚀ꀊጿἢઔܛ᎓Ե⅜㛵጑ྣᏝ༱⮢ΫÊ㕮⽹','繁Ɇʓӿꩭਸꆟꑇ㳋Ήᴝ㕨㰵ꇳ');

// binary
$testValues['binary'] = array(0x3AD2BBC2, 720, 0xEED3A109F8F7745C, 0xD6C3E0E11A25F,0x4EACCEF38788F9,0xFFFFFFFFFFFFFFFF,6230974598,0x44E4A);
$testValues['varbinary'] = array(58342,0xF3ED38AAC3CDC87759DE34B23C223CCDAB42109FBC888,0xE4300FF,0x000005ED309D3A45,0x06CADE379,804,0x00000000000000,0xD7209FFE4);
$testValues['varbinary(max)'] = array(0xEF409CB33408, 0xD3EA762C78F,0,0xFFFFFFFFFFFFFFFFFFFFFFFFF,
0x582D40EF3EB4E9762C5AA49D4E40C42CB4009ED3E75F890A2FD14BF495EFF5378A23BB782C4A40E1D0005DA3FE208A48C1F,
0x38054,9230094389109,0xDDD4D88C4B80089D2E4A,0x88F8A8);

// The comparison operators to test
$comparisons = array('=', '<', '>', '<=', '>=', '<>', '!<', '!>');

// Thresholds against which to use the comparison operators
$thresholds = array('integer' => 0,
'bigint' => 0,
'smallint' => 1000,
'tinyint' => 100,
'bit' => 0,
'float' => 1.2,
'real' => -1.2,
'numeric' => 45.6789,
'char' => 'rstuv',
'nchar' => '㊃ᾞਲ㨴꧶ꁚꅍ',
'varchar' => '6$gt?je#~',
'nvarchar' => 'ӕᏵ൴ꔓὀ⾼',
'varchar(max)' => 'hijkl',
'nvarchar(max)' => 'xᐕᛙᘡ',
'binary' => 0x44E4A,
'varbinary' => 0xE4300FF,
'varbinary(max)' => 0xD3EA762C78F,
'date' => '2010-01-31',
'time' => '21:45:45.4545',
'datetime' => '3125-05-31 05:00:32.4',
'datetime2' => '2384-12-31 12:40:12.5434323',
'datetimeoffset' => '1984-09-25 10:40:20.0909111+03:00',
'smalldatetime' => '1998-06-13 04:00:30',
);

// String patterns to test with LIKE
$patterns = array('integer' => array('8', '48', '123'),
'bigint' => array('000','7', '65536'),
'smallint' => array('4','768','abc'),
'tinyint' => array('9','0','25'),
'bit' => array('0','1','100'),
'float' => array('14159','.','E+','2.3','308'),
'real' => array('30','.','e-','2.3','38'),
'numeric' => array('0','0000','12345','abc','.'),
'char' => array('w','@','x*fv=u$','e3'),
'nchar' => array('af㋮','㐯ꋛ㗾','ꦣ㧼ለͭ','123'),
'varchar' => array(' ','a','#','@@)'),
'nvarchar' => array('ӕ','Ӿϰᇬ㭡','璉Džꖭ갪ụ⺭','更ꧥ','ꈔꇼŞ'),
'varchar(max)' => array('A','|*7Y&','4z','@!@','AjE'),
'nvarchar(max)' => array('t','㧶ᐁቴƯɋ','ᘷ㬡',' ','ꐾɔᡧ㝚'),
'binary' => array('0x44E4A'),
'varbinary' => array('0xE4300FF'),
'varbinary(max)' => array('0xD3EA762C78F'),
'date' => array('20','%','9-','04'),
'time' => array('4545','.0','20:','12345',':'),
'datetime' => array('997','12',':5','9999'),
'datetime2' => array('3125-05-31 05:','.45','$f#','-29 ','0001'),
'datetimeoffset' => array('+02','96',' ','5092856',':00'),
'smalldatetime' => array('00','1999','abc',':','06'),
);
?>
2 changes: 2 additions & 0 deletions test/functional/pdo_sqlsrv/MsSetup.inc
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ $AKVPassword = 'TARGET_AKV_PASSWORD'; // for use with KeyVaultPasswo
$AKVClientID = 'TARGET_AKV_CLIENT_ID'; // for use with KeyVaultClientSecret
$AKVSecret = 'TARGET_AKV_CLIENT_SECRET'; // for use with KeyVaultClientSecret

// for enclave computations
$attestation = 'TARGET_ATTESTATION';
?>
Loading

0 comments on commit 0513287

Please sign in to comment.