Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always Encrypted v2 support #1045

Merged
merged 21 commits into from
Oct 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';

yitam marked this conversation as resolved.
Show resolved Hide resolved
// 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',
);

yitam marked this conversation as resolved.
Show resolved Hide resolved
// 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();

david-puglielli marked this conversation as resolved.
Show resolved Hide resolved
// 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
yitam marked this conversation as resolved.
Show resolved Hide resolved
$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);

yitam marked this conversation as resolved.
Show resolved Hide resolved
// 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